@uniai-fe/uds-primitives 0.3.53 → 0.3.55

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
@@ -533,7 +533,7 @@
533
533
  --select-table-border-default-color: var(--input-border-table-default-color);
534
534
  --select-table-border-focus-color: var(--input-border-active-color);
535
535
  --select-table-border-error-color: var(--input-border-error-color);
536
- --select-table-border-disabled-color: var(--input-border-disabled-color);
536
+ --select-table-border-disabled-color: transparent;
537
537
  --select-table-border-readonly-color: transparent;
538
538
  --select-table-surface-color: transparent;
539
539
  --select-table-surface-disabled-color: var(--input-surface-disabled-color);
@@ -594,6 +594,7 @@
594
594
  );
595
595
  --select-primary-color-surface: var(--input-surface-color);
596
596
  --select-primary-color-text: var(--color-label-alternative);
597
+ --select-color-text-value: var(--color-label-strong);
597
598
  --select-primary-color-text-focused: var(--color-label-strong);
598
599
  --select-primary-color-text-disabled: var(--color-label-disabled);
599
600
  --select-primary-color-text-readonly: var(--color-label-strong);
@@ -2777,6 +2778,11 @@ figure.chip {
2777
2778
  box-shadow: none;
2778
2779
  }
2779
2780
 
2781
+ .input-field[data-has-value=true] .input-element {
2782
+ color: var(--input-text-color);
2783
+ caret-color: var(--input-text-color);
2784
+ }
2785
+
2780
2786
  .input-field-control {
2781
2787
  display: flex;
2782
2788
  align-items: center;
@@ -2809,9 +2815,24 @@ figure.chip {
2809
2815
  --input-textarea-gap: var(--input-textarea-gap-large);
2810
2816
  }
2811
2817
 
2818
+ .input[data-input-type=textarea] {
2819
+ min-height: var(--input-textarea-height);
2820
+ }
2821
+ .input[data-input-type=textarea] .input-box {
2822
+ height: 100%;
2823
+ }
2824
+ .input[data-input-type=textarea] .input-field {
2825
+ height: 100%;
2826
+ }
2827
+ .input[data-input-type=textarea] .input-field-control {
2828
+ flex: 1 1 auto;
2829
+ width: 100%;
2830
+ align-items: stretch;
2831
+ }
2832
+
2812
2833
  .input-textarea-element {
2813
2834
  min-height: var(--input-textarea-min-height);
2814
- height: var(--input-textarea-height);
2835
+ height: 100%;
2815
2836
  resize: none;
2816
2837
  margin: 0;
2817
2838
  }
@@ -4046,9 +4067,6 @@ figure.chip {
4046
4067
  color: var(--select-primary-color-text-readonly);
4047
4068
  caret-color: var(--select-primary-color-text-readonly);
4048
4069
  }
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
4070
  .select-button:where([data-priority=secondary]):where([data-state=disabled]) .select-input-label {
4053
4071
  color: var(--select-secondary-color-text-disabled);
4054
4072
  }
@@ -4056,6 +4074,11 @@ figure.chip {
4056
4074
  color: var(--select-secondary-color-text-readonly);
4057
4075
  }
4058
4076
 
4077
+ .select-value[data-has-value=true] .select-input-label {
4078
+ color: var(--select-color-text-value);
4079
+ caret-color: var(--select-color-text-value);
4080
+ }
4081
+
4059
4082
  .select-input-label-placeholder {
4060
4083
  color: var(--select-primary-color-placeholder);
4061
4084
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-primitives",
3
- "version": "0.3.53",
3
+ "version": "0.3.55",
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
  >
@@ -279,6 +279,11 @@
279
279
  }
280
280
  }
281
281
 
282
+ .input-field[data-has-value="true"] .input-element {
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
  }
@@ -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,11 @@
302
297
  }
303
298
  }
304
299
 
300
+ .select-value[data-has-value="true"] .select-input-label {
301
+ color: var(--select-color-text-value);
302
+ caret-color: var(--select-color-text-value);
303
+ }
304
+
305
305
  .select-input-label-placeholder {
306
306
  color: var(--select-primary-color-placeholder);
307
307
 
@@ -36,7 +36,7 @@
36
36
  --select-table-border-default-color: var(--input-border-table-default-color);
37
37
  --select-table-border-focus-color: var(--input-border-active-color);
38
38
  --select-table-border-error-color: var(--input-border-error-color);
39
- --select-table-border-disabled-color: var(--input-border-disabled-color);
39
+ --select-table-border-disabled-color: transparent;
40
40
  --select-table-border-readonly-color: transparent;
41
41
  --select-table-surface-color: transparent;
42
42
  --select-table-surface-disabled-color: var(--input-surface-disabled-color);
@@ -99,6 +99,7 @@
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);
@@ -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
  */
@@ -7,6 +7,7 @@ import TableHead from "./foundation/Head";
7
7
  import TableTh from "./foundation/Th";
8
8
  import TableRoot from "./foundation/Root";
9
9
  import TableRow from "./foundation/Row";
10
+ import { Slot } from "../../slot/markup";
10
11
 
11
12
  /**
12
13
  * Table Preset; 기본 Container 조합 컴포넌트
@@ -45,6 +46,8 @@ const TableContainer = forwardRef<HTMLTableElement, TableContainerProps>(
45
46
  isCustomBody = false,
46
47
  scrollable = false,
47
48
  scrollAxis = "x",
49
+ scrollAs = "div",
50
+ scrollProps,
48
51
  scrollClassName,
49
52
  footer,
50
53
  children,
@@ -153,14 +156,16 @@ const TableContainer = forwardRef<HTMLTableElement, TableContainerProps>(
153
156
  }
154
157
 
155
158
  return (
156
- <div
159
+ <Slot.Base
160
+ as={scrollAs}
157
161
  className={clsx("table-scroll-wrapper", scrollClassName)}
158
162
  data-layout={tableProps?.layout ?? "line"}
159
163
  data-role={tableProps?.role ?? "table"}
160
164
  data-scroll-axis={scrollAxis}
165
+ {...scrollProps}
161
166
  >
162
167
  {tableNode}
163
- </div>
168
+ </Slot.Base>
164
169
  );
165
170
  },
166
171
  );
@@ -1,5 +1,5 @@
1
- import type { ComponentPropsWithoutRef } from "react";
2
- import type { SlotTextProps } from "../../slot";
1
+ import type { ComponentPropsWithoutRef, ElementType } from "react";
2
+ import type { SlotComponentProps, SlotTextProps } from "../../slot";
3
3
 
4
4
  export const TABLE_CELL_ALIGN_OPTIONS = ["left", "center", "right"] as const;
5
5
  export const TABLE_CELL_ALIGN_Y_OPTIONS = ["top", "center", "bottom"] as const;
@@ -154,6 +154,8 @@ export interface TableColumnData<
154
154
  * @property {boolean} [isCustomBody] true면 body wrapper 없이 children을 직접 렌더링
155
155
  * @property {boolean} [scrollable=false] true면 외부 스크롤 래퍼를 추가한다.
156
156
  * @property {"x" | "y" | "both"} [scrollAxis="x"] scrollable일 때 스크롤 축
157
+ * @property {ElementType} [scrollAs="div"] scrollable wrapper element
158
+ * @property {Omit<SlotComponentProps<ElementType>, "as" | "children" | "className">} [scrollProps] scrollable wrapper native props
157
159
  * @property {string} [scrollClassName] 스크롤 래퍼 className
158
160
  * @property {React.ReactNode} [footer] footer 노드
159
161
  * @property {React.ReactNode} [children] body 콘텐츠
@@ -177,6 +179,17 @@ export interface TableContainerProps<
177
179
  * scrollable일 때 스크롤 축
178
180
  */
179
181
  scrollAxis?: TableScrollAxis;
182
+ /**
183
+ * scrollable wrapper element
184
+ */
185
+ scrollAs?: ElementType;
186
+ /**
187
+ * scrollable wrapper native props
188
+ */
189
+ scrollProps?: Omit<
190
+ SlotComponentProps<ElementType>,
191
+ "as" | "children" | "className"
192
+ >;
180
193
  /**
181
194
  * 스크롤 래퍼 className
182
195
  */