@uniai-fe/uds-primitives 0.2.8 → 0.2.10

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 (72) hide show
  1. package/dist/styles.css +419 -0
  2. package/package.json +2 -1
  3. package/src/components/calendar/types/calendar.ts +5 -0
  4. package/src/components/dropdown/markup/Template.tsx +41 -17
  5. package/src/components/dropdown/markup/foundation/Container.tsx +14 -2
  6. package/src/components/dropdown/markup/foundation/MenuItem.tsx +20 -6
  7. package/src/components/dropdown/markup/foundation/Root.tsx +8 -1
  8. package/src/components/dropdown/markup/foundation/Trigger.tsx +7 -1
  9. package/src/components/dropdown/styles/dropdown.scss +4 -0
  10. package/src/components/dropdown/types/props.ts +5 -0
  11. package/src/components/input/markup/date/Template.tsx +36 -5
  12. package/src/components/input/markup/date/Trigger.tsx +22 -4
  13. package/src/components/input/markup/foundation/Input.tsx +19 -11
  14. package/src/components/input/markup/foundation/Utility.tsx +11 -7
  15. package/src/components/input/styles/date.scss +21 -0
  16. package/src/components/input/styles/foundation.scss +30 -0
  17. package/src/components/input/styles/variables.scss +11 -0
  18. package/src/components/input/types/date.ts +15 -0
  19. package/src/components/input/types/foundation.ts +18 -11
  20. package/src/components/input/utils/date.ts +15 -1
  21. package/src/components/select/hooks/index.ts +1 -45
  22. package/src/components/select/hooks/interaction.ts +62 -0
  23. package/src/components/select/markup/Default.tsx +59 -35
  24. package/src/components/select/markup/foundation/Base.tsx +12 -4
  25. package/src/components/select/markup/foundation/Container.tsx +37 -34
  26. package/src/components/select/markup/foundation/Icon.tsx +6 -1
  27. package/src/components/select/markup/multiple/Multiple.tsx +62 -35
  28. package/src/components/select/markup/multiple/SelectedChip.tsx +5 -2
  29. package/src/components/select/styles/select.scss +50 -0
  30. package/src/components/select/styles/variables.scss +26 -0
  31. package/src/components/select/types/base.ts +3 -2
  32. package/src/components/select/types/icon.ts +7 -6
  33. package/src/components/select/types/index.ts +1 -0
  34. package/src/components/select/types/interaction.ts +30 -0
  35. package/src/components/select/types/props.ts +8 -0
  36. package/src/components/select/types/trigger.ts +4 -0
  37. package/src/components/table/hooks/index.ts +0 -3
  38. package/src/components/table/index.tsx +5 -3
  39. package/src/components/table/markup/Container.tsx +126 -0
  40. package/src/components/table/markup/foundation/Body.tsx +24 -0
  41. package/src/components/table/markup/foundation/Cell.tsx +72 -0
  42. package/src/components/table/markup/foundation/Col.tsx +22 -0
  43. package/src/components/table/markup/foundation/Colgroup.tsx +29 -0
  44. package/src/components/table/markup/foundation/Foot.tsx +24 -0
  45. package/src/components/table/markup/foundation/Head.tsx +24 -0
  46. package/src/components/table/markup/foundation/Root.tsx +32 -0
  47. package/src/components/table/markup/foundation/Row.tsx +32 -0
  48. package/src/components/table/markup/foundation/Td.tsx +37 -0
  49. package/src/components/table/markup/foundation/Th.tsx +39 -0
  50. package/src/components/table/markup/foundation/index.tsx +30 -0
  51. package/src/components/table/markup/index.tsx +8 -2
  52. package/src/components/table/styles/foundation.scss +247 -0
  53. package/src/components/table/styles/index.scss +2 -0
  54. package/src/components/table/styles/variables.scss +29 -0
  55. package/src/components/table/types/foundation.ts +250 -0
  56. package/src/components/table/types/index.ts +1 -4
  57. package/src/components/tooltip/img/info.svg +5 -0
  58. package/src/components/tooltip/img/information.svg +9 -0
  59. package/src/components/tooltip/index.scss +1 -0
  60. package/src/components/tooltip/index.tsx +4 -0
  61. package/src/components/tooltip/markup/Message.tsx +70 -0
  62. package/src/components/tooltip/markup/Root.tsx +32 -0
  63. package/src/components/tooltip/markup/Template.tsx +46 -0
  64. package/src/components/tooltip/markup/Trigger.tsx +32 -0
  65. package/src/components/tooltip/markup/index.tsx +18 -0
  66. package/src/components/tooltip/styles/index.scss +2 -0
  67. package/src/components/tooltip/styles/tooltip.scss +47 -0
  68. package/src/components/tooltip/styles/variables.scss +14 -0
  69. package/src/components/tooltip/types/index.ts +1 -0
  70. package/src/components/tooltip/types/props.ts +118 -0
  71. package/src/index.scss +1 -0
  72. package/src/index.tsx +1 -0
@@ -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}
@@ -7,11 +7,18 @@ import type { ReactNode } from "react";
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
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,
@@ -8,10 +8,16 @@ 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
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
22
  const DropdownTrigger = forwardRef<HTMLElement, DropdownMenuTriggerProps>(
17
23
  ({ asChild = true, children, ...rest }, ref) => {
@@ -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
  }
@@ -138,6 +138,7 @@ export interface DropdownTemplateItem {
138
138
  * @property {DropdownMenuProps} [rootProps] Root 에 전달할 props
139
139
  * @property {DropdownContainerProps} [containerProps] Container 에 전달할 props
140
140
  * @property {DropdownMenuListProps} [menuListProps] MenuList 에 전달할 props
141
+ * @property {ReactNode} [alt] item이 비어 있을 때 렌더링할 alternate 콘텐츠
141
142
  */
142
143
  export interface DropdownTemplateProps {
143
144
  trigger: ReactNode;
@@ -159,4 +160,8 @@ export interface DropdownTemplateProps {
159
160
  * MenuList 에 전달할 props
160
161
  */
161
162
  menuListProps?: DropdownMenuListProps;
163
+ /**
164
+ * item이 비어 있을 때 렌더링할 alternate 콘텐츠
165
+ */
166
+ alt?: ReactNode;
162
167
  }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import type { ChangeEvent, MouseEvent as ReactMouseEvent } from "react";
4
- import { forwardRef, useCallback, useMemo } from "react";
4
+ import { forwardRef, useCallback, useMemo, useState } from "react";
5
5
  import { useUncontrolled } from "@mantine/hooks";
6
6
  import clsx from "clsx";
7
7
  import { Calendar } from "../../../calendar";
@@ -19,8 +19,13 @@ import {
19
19
  import InputDateFooterTemplate from "./footer/Template";
20
20
  import InputDateTrigger from "./Trigger";
21
21
 
22
+ const INPUT_DATE_TABLE_FORMAT = "YY-MM-DD";
23
+
22
24
  /**
23
25
  * Input Date Template; trigger + calendar 조합.
26
+ * priority가 table이면 trigger 표시 포맷/DatePicker 기본 valueFormat을 `YY-MM-DD`로 맞춘다.
27
+ * placeholder는 format과 별도 props로 처리되며, format 변경으로 placeholder가 자동 치환되지는 않는다.
28
+ * 단, `datePickerProps.valueFormat`이 주어지면 해당 포맷을 우선한다.
24
29
  * @component
25
30
  * @param {InputCalendarProps} props
26
31
  * @param {CalendarMode} [props.mode="date"] 날짜/시간 모드
@@ -35,6 +40,7 @@ import InputDateTrigger from "./Trigger";
35
40
  * @param {string} [props.name] form name/RHF name
36
41
  * @param {UseFormRegisterReturn} [props.register] RHF register
37
42
  * @param {string} [props.placeholder="YYYY-MM-DD"] placeholder
43
+ * @param {"primary" | "secondary" | "tertiary" | "table"} [props.priority="primary"] trigger input priority
38
44
  * @param {ReactNode} [props.header] 패널 header 콘텐츠
39
45
  * @param {ReactNode} [props.footer] 패널 footer 콘텐츠
40
46
  * @param {unknown} [props.timePicker] TimePicker 확장용 예약 슬롯(현재 미구현)
@@ -60,6 +66,7 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
60
66
  name,
61
67
  register,
62
68
  placeholder = "YYYY-MM-DD",
69
+ priority = "primary",
63
70
  className,
64
71
  header,
65
72
  footer,
@@ -71,6 +78,10 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
71
78
  },
72
79
  ref,
73
80
  ) => {
81
+ // 기본(비제어) calendar open 상태를 관리한다.
82
+ const [internalCalendarOpen, setInternalCalendarOpen] = useState(false);
83
+ const resolvedCalendarOpen = calendarOpened ?? internalCalendarOpen;
84
+
74
85
  // useUncontrolled로 제어형/비제어 값을 모두 허용한다.
75
86
  const [calendarValue, setCalendarValue] = useUncontrolled<CalendarValue>({
76
87
  value,
@@ -113,10 +124,25 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
113
124
  );
114
125
 
115
126
  const triggerValue = useMemo(
116
- () => formatTriggerValue(calendarValue) ?? "",
117
- [calendarValue],
127
+ () =>
128
+ formatTriggerValue(
129
+ calendarValue,
130
+ priority === "table" ? INPUT_DATE_TABLE_FORMAT : undefined,
131
+ ) ?? "",
132
+ [calendarValue, priority],
118
133
  );
119
134
 
135
+ const resolvedDatePickerProps = useMemo(() => {
136
+ if (priority !== "table" || datePickerProps?.valueFormat) {
137
+ return datePickerProps;
138
+ }
139
+ // table priority는 yy-MM-dd 포맷을 기본값으로 둔다.
140
+ return {
141
+ ...datePickerProps,
142
+ valueFormat: INPUT_DATE_TABLE_FORMAT,
143
+ };
144
+ }, [datePickerProps, priority]);
145
+
120
146
  const handleTriggerClick = (event: ReactMouseEvent<Element>) => {
121
147
  triggerOnClick?.(event);
122
148
  };
@@ -131,6 +157,8 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
131
157
  disabled={disabled}
132
158
  onClear={() => updateValue(createEmptyValue())}
133
159
  onToday={() => updateValue(getTodayValue())}
160
+ // 변경: Apply 버튼 클릭 시 calendar를 닫는다.
161
+ onApply={() => setInternalCalendarOpen(false)}
134
162
  />
135
163
  );
136
164
 
@@ -141,6 +169,7 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
141
169
  displayValue: triggerValue,
142
170
  disabled,
143
171
  onClick: handleTriggerClick,
172
+ priority,
144
173
  };
145
174
 
146
175
  const triggerNode = trigger ?? renderTrigger?.(triggerRenderProps) ?? (
@@ -152,6 +181,7 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
152
181
  displayValue={triggerValue}
153
182
  disabled={disabled}
154
183
  onClick={handleTriggerClick}
184
+ priority={priority}
155
185
  />
156
186
  );
157
187
 
@@ -165,10 +195,11 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
165
195
  readOnly={readOnly}
166
196
  value={calendarValue}
167
197
  onChange={handleCalendarChange}
168
- datePickerProps={datePickerProps}
198
+ datePickerProps={resolvedDatePickerProps}
169
199
  header={header}
170
200
  footer={footerContent}
171
- open={calendarOpened}
201
+ open={resolvedCalendarOpen}
202
+ onOpenChange={setInternalCalendarOpen}
172
203
  >
173
204
  {triggerNode}
174
205
  </Calendar.Root>
@@ -9,6 +9,8 @@ import type { InputCalendarTriggerViewProps } from "../../types";
9
9
 
10
10
  /**
11
11
  * Input Date trigger field.
12
+ * priority가 table인 경우 아이콘을 left 슬롯에 배치하고,
13
+ * 그 외 priority에서는 기존처럼 right 슬롯을 유지한다.
12
14
  * @component
13
15
  * @param {InputCalendarTriggerViewProps} props
14
16
  * @param {string} [props.className] trigger className
@@ -16,6 +18,7 @@ import type { InputCalendarTriggerViewProps } from "../../types";
16
18
  * @param {string} [props.displayValue] 표시 문자열
17
19
  * @param {(event: MouseEvent<Element>) => void} [props.onClick] 클릭 핸들러
18
20
  * @param {boolean} [props.disabled] disabled 여부
21
+ * @param {"primary" | "secondary" | "tertiary" | "table"} [props.priority] trigger input priority
19
22
  * @param {string} [props.id] input id
20
23
  * @param {string} [props.name] form name
21
24
  * @param {UseFormRegisterReturn} [props.register] RHF register
@@ -31,6 +34,7 @@ const InputDateTrigger = forwardRef<
31
34
  displayValue,
32
35
  onClick,
33
36
  disabled,
37
+ priority,
34
38
  id,
35
39
  name,
36
40
  register,
@@ -55,19 +59,33 @@ const InputDateTrigger = forwardRef<
55
59
  {...restProps}
56
60
  ref={ref}
57
61
  value={displayValue}
62
+ // PopOver.Trigger(asChild)가 주입한 `type="button"`으로 placeholder가 사라지는 문제를 막기 위해
63
+ // Date trigger는 항상 readOnly text input으로 고정한다.
64
+ type="text"
58
65
  readOnly
59
66
  disabled={disabled}
60
67
  id={id}
61
68
  name={name ?? register?.name}
62
69
  register={register}
70
+ priority={priority}
63
71
  placeholder={placeholder}
64
72
  className={clsx("input-date-trigger-input", className)}
65
73
  onClick={handleInputClick}
74
+ // table priority는 icon을 왼쪽 슬롯에 배치한다.
75
+ left={
76
+ priority === "table" ? (
77
+ <figure className="input-date-trigger-icon" aria-hidden="true">
78
+ <Calendar.Icon.Calendar />
79
+ </figure>
80
+ ) : undefined
81
+ }
82
+ // 기본(priority != table)은 기존처럼 icon을 오른쪽 슬롯에 유지한다.
66
83
  right={
67
- // 토글 인터랙션은 input 단일 경로로 유지하고, 오른쪽 아이콘은 장식 요소로만 둔다.
68
- <figure className="input-date-trigger-icon" aria-hidden="true">
69
- <Calendar.Icon.Calendar />
70
- </figure>
84
+ priority === "table" ? undefined : (
85
+ <figure className="input-date-trigger-icon" aria-hidden="true">
86
+ <Calendar.Icon.Calendar />
87
+ </figure>
88
+ )
71
89
  }
72
90
  />
73
91
  );
@@ -29,15 +29,16 @@ import InputBaseUtil from "./Utility";
29
29
  *
30
30
  * @component
31
31
  * @param {InputProps} props Input 컴포넌트 공통 props
32
- * @param {"primary" | "secondary" | "tertiary"} [props.priority="primary"] 디자인 토큰 우선순위
32
+ * @param {"primary" | "secondary" | "tertiary" | "table"} [props.priority="primary"] 디자인 토큰 우선순위
33
33
  * @param {"small" | "medium" | "large"} [props.size="medium"] 높이/타이포 세트
34
- * @param {"default" | "active" | "focused" | "success" | "error" | "disabled"} [props.state="default"] 시각 상태
35
- * @param {boolean} [props.block=false] true면 width 100%
34
+ * @param {"default" | "active" | "focused" | "success" | "error" | "disabled" | "loading"} [props.state="default"] 시각 상태
35
+ * @param {boolean} [props.block=false] true면 width 100% (`priority="table"`은 width 미지정 시 full 기본)
36
+ * @param {FormFieldWidth} [props.width] width preset
36
37
  * @param {React.ReactNode} [props.left] 입력 왼쪽 슬롯(아이콘/텍스트)
37
38
  * @param {React.ReactNode} [props.right] 입력 오른쪽 슬롯
38
- * @param {React.ReactNode} [props.clearIcon] 입력값 초기화 아이콘. 지정하지 않으면 기본 Reset 아이콘
39
- * @param {React.ReactNode} [props.successIcon] success 상태 아이콘 override
40
- * @param {React.ReactNode} [props.errorIcon] error 상태 아이콘 override
39
+ * @param {React.ReactNode} [props.clear] 입력값 초기화 아이콘. 지정하지 않으면 기본 Reset 아이콘
40
+ * @param {React.ReactNode} [props.success] success 상태 아이콘 override
41
+ * @param {React.ReactNode} [props.error] error 상태 아이콘 override
41
42
  * @param {string} [props.inputClassName] 실제 `<input>` 요소 className
42
43
  * @param {string} [props.boxClassName] `.input-box` className
43
44
  * @param {boolean} [props.disabled] native disabled
@@ -52,6 +53,7 @@ import InputBaseUtil from "./Utility";
52
53
  * @param {string | number | readonly string[]} [props.value] 제어형 값
53
54
  * @param {string | number | readonly string[]} [props.defaultValue] 비제어 초기값
54
55
  * @param {string} [props.type="text"] native input type
56
+ * @param {boolean} [props.readOnly] native readOnly
55
57
  */
56
58
  const InputBase = forwardRef<HTMLInputElement, InputProps>(
57
59
  (
@@ -84,6 +86,10 @@ const InputBase = forwardRef<HTMLInputElement, InputProps>(
84
86
  },
85
87
  ref,
86
88
  ) => {
89
+ // table priority는 width 지정이 없으면 셀 가로폭을 기본으로 채운다.
90
+ const isTablePriority = priority === "table";
91
+ const resolvedBlock = block || (isTablePriority && width === undefined);
92
+
87
93
  const generatedId = useId();
88
94
  const registerRef = register?.ref;
89
95
  const registerOnChange = register?.onChange;
@@ -174,7 +180,7 @@ const InputBase = forwardRef<HTMLInputElement, InputProps>(
174
180
  const widthAttr =
175
181
  width !== undefined
176
182
  ? getFormFieldWidthAttr(width)
177
- : block
183
+ : resolvedBlock
178
184
  ? "full"
179
185
  : undefined;
180
186
  const widthValue =
@@ -191,13 +197,13 @@ const InputBase = forwardRef<HTMLInputElement, InputProps>(
191
197
  `input-priority-${priority}`,
192
198
  `input-size-${size}`,
193
199
  `input-state-${visualState}`,
194
- block && "input-block",
200
+ resolvedBlock && "input-block",
195
201
  className,
196
202
  )}
197
203
  data-priority={priority}
198
204
  data-size={size}
199
205
  data-state={visualState}
200
- data-block={block ? "true" : undefined}
206
+ data-block={resolvedBlock ? "true" : undefined}
201
207
  {...(simulatedState ? { "data-simulated-state": simulatedState } : {})}
202
208
  data-width={widthAttr}
203
209
  style={containerStyle}
@@ -208,7 +214,7 @@ const InputBase = forwardRef<HTMLInputElement, InputProps>(
208
214
  `input-box-priority-${priority}`,
209
215
  `input-box-size-${size}`,
210
216
  `input-box-state-${visualState}`,
211
- block && "input-box-block",
217
+ resolvedBlock && "input-box-block",
212
218
  boxClassNameProp,
213
219
  )}
214
220
  data-slot="box"
@@ -218,7 +224,7 @@ const InputBase = forwardRef<HTMLInputElement, InputProps>(
218
224
  data-state={visualState}
219
225
  data-priority={priority}
220
226
  data-size={size}
221
- data-block={block ? "true" : undefined}
227
+ data-block={resolvedBlock ? "true" : undefined}
222
228
  >
223
229
  {/* 필드 컨트롤 wrapper; 슬롯과 input 정렬 */}
224
230
  <div className="input-field-control">
@@ -242,6 +248,8 @@ const InputBase = forwardRef<HTMLInputElement, InputProps>(
242
248
  />
243
249
  </div>
244
250
  <InputBaseUtil
251
+ // table priority는 success/error 상태 아이콘 대신 border만으로 상태를 표현한다.
252
+ priority={priority}
245
253
  state={currentState}
246
254
  isDisabled={isDisabled}
247
255
  isFocused={isFocused}
@@ -9,23 +9,25 @@ import InputBaseSideSlot from "./SideSlot";
9
9
  /**
10
10
  * Input; 오른쪽 유틸리티 영역(wrapper)
11
11
  * @component
12
- * @param {InputUtilProps} props
13
- * @param {React.ReactNode} [props.right] 오른쪽 슬롯 콘텐츠
12
+ * @param {InputUtilityProps} props
13
+ * @param {React.ReactNode} [props.children] 오른쪽 슬롯 콘텐츠
14
14
  * @param {React.ReactNode} [props.clear] clear 버튼 아이콘
15
15
  * @param {React.ReactNode} [props.success] success 상태 아이콘 override
16
16
  * @param {React.ReactNode} [props.error] error 상태 아이콘 override
17
- * @param {string} props.state 현재 input 상태
17
+ * @param {"primary" | "secondary" | "tertiary" | "table"} props.priority input priority
18
+ * @param {"default" | "active" | "focused" | "success" | "error" | "disabled" | "loading"} props.state 현재 input 상태
18
19
  * @param {boolean} props.isDisabled disable 여부
19
20
  * @param {boolean} props.isFocused focus 여부
20
21
  * @param {boolean} props.hasValue 입력값 존재 여부
21
22
  * @param {boolean} [props.readOnly] readOnly 여부
22
- * @param {Function} [props.onClear] clear 버튼 클릭 핸들러
23
+ * @param {(event: React.MouseEvent<HTMLButtonElement> | React.PointerEvent<HTMLButtonElement>) => void} [props.onClear] clear 버튼 클릭 핸들러
23
24
  */
24
25
  export default function InputBaseUtil({
25
26
  children: right,
26
27
  clear,
27
28
  success,
28
29
  error,
30
+ priority,
29
31
  state,
30
32
  isDisabled,
31
33
  isFocused,
@@ -42,6 +44,8 @@ export default function InputBaseUtil({
42
44
  : state === "error"
43
45
  ? (error ?? baseStatusIcon)
44
46
  : null;
47
+ // table priority는 상태를 border로만 표현하므로 status icon을 렌더하지 않는다.
48
+ const resolvedStatusIcon = priority === "table" ? null : statusIcon;
45
49
  const clearIconNode = clear ?? InputStatusIcon.reset;
46
50
  const showClearIcon = Boolean(
47
51
  clearIconNode &&
@@ -50,7 +54,7 @@ export default function InputBaseUtil({
50
54
  (isFocused || isClearInteracting),
51
55
  );
52
56
 
53
- if (!right && !showClearIcon && !statusIcon) {
57
+ if (!right && !showClearIcon && !resolvedStatusIcon) {
54
58
  return null;
55
59
  }
56
60
 
@@ -91,13 +95,13 @@ export default function InputBaseUtil({
91
95
  {clearIconNode}
92
96
  </button>
93
97
  ) : null}
94
- {statusIcon ? (
98
+ {resolvedStatusIcon ? (
95
99
  <div
96
100
  className="input-affix input-affix-status"
97
101
  data-slot="status"
98
102
  data-state={state}
99
103
  >
100
- {statusIcon}
104
+ {resolvedStatusIcon}
101
105
  </div>
102
106
  ) : null}
103
107
  </div>
@@ -21,6 +21,27 @@
21
21
  pointer-events: none;
22
22
  }
23
23
 
24
+ .input-date-trigger-input.input-priority-table {
25
+ .input-field {
26
+ padding-inline: var(--spacing-padding-4);
27
+ }
28
+
29
+ .input-field-control {
30
+ gap: var(--spacing-gap-3);
31
+ }
32
+
33
+ .input-element {
34
+ font-size: var(--font-body-xsmall-size);
35
+ line-height: var(--font-body-xsmall-line-height);
36
+ font-weight: var(--font-body-xsmall-weight);
37
+ }
38
+
39
+ .input-date-trigger-icon {
40
+ // input date 아이콘 크기는 size/priority와 무관하게 공통 크기를 유지한다.
41
+ color: var(--color-label-alternative);
42
+ }
43
+ }
44
+
24
45
  .input-date-footer-template {
25
46
  // footer 템플릿은 input 스킨에서만 관리한다.
26
47
  display: grid;
@@ -154,6 +154,13 @@
154
154
  }
155
155
  }
156
156
 
157
+ &[data-priority="table"] {
158
+ // table priority는 default에서 border를 숨기고 상태(border-color)만 드러낸다.
159
+ border-radius: var(--input-table-radius-base);
160
+ border-color: var(--input-border-table-default-color);
161
+ background-color: transparent;
162
+ }
163
+
157
164
  &:not([data-priority="secondary"]) {
158
165
  &[data-state="active"],
159
166
  &[data-state="focused"] {
@@ -252,6 +259,24 @@
252
259
  min-height: var(--theme-size-medium-2);
253
260
  }
254
261
 
262
+ .input-field[data-priority="table"][data-size="small"] .input-element {
263
+ font-size: var(--input-table-text-small-size);
264
+ line-height: var(--input-table-text-small-line-height);
265
+ font-weight: var(--input-table-text-small-weight);
266
+ }
267
+
268
+ .input-field[data-priority="table"][data-size="medium"] .input-element {
269
+ font-size: var(--input-table-text-medium-size);
270
+ line-height: var(--input-table-text-medium-line-height);
271
+ font-weight: var(--input-table-text-medium-weight);
272
+ }
273
+
274
+ .input-field[data-priority="table"][data-size="large"] .input-element {
275
+ font-size: var(--input-table-text-large-size);
276
+ line-height: var(--input-table-text-large-line-height);
277
+ font-weight: var(--input-table-text-large-weight);
278
+ }
279
+
255
280
  .input-helper-text {
256
281
  color: var(--input-helper-color);
257
282
  font-size: var(--font-label-small-size);
@@ -362,6 +387,11 @@
362
387
  &[data-priority="secondary"] {
363
388
  background-color: transparent;
364
389
  }
390
+
391
+ &[data-priority="table"] {
392
+ // table priority는 disabled 상태에서도 배경을 투명하게 유지한다.
393
+ background-color: transparent;
394
+ }
365
395
  }
366
396
  }
367
397
 
@@ -13,6 +13,16 @@
13
13
  --input-default-gap: var(--spacing-gap-4);
14
14
  --input-default-radius-base: var(--theme-radius-large-1);
15
15
  --input-tertiary-radius-base: var(--theme-radius-large-2);
16
+ --input-table-radius-base: 0;
17
+ --input-table-text-small-size: var(--font-body-xxsmall-size);
18
+ --input-table-text-small-line-height: var(--font-body-xxsmall-line-height);
19
+ --input-table-text-small-weight: var(--font-body-xxsmall-weight);
20
+ --input-table-text-medium-size: var(--font-body-xsmall-size);
21
+ --input-table-text-medium-line-height: var(--font-body-xsmall-line-height);
22
+ --input-table-text-medium-weight: var(--font-body-xsmall-weight);
23
+ --input-table-text-large-size: var(--font-body-small-size);
24
+ --input-table-text-large-line-height: var(--font-body-small-line-height);
25
+ --input-table-text-large-weight: var(--font-body-small-weight);
16
26
 
17
27
  /* Label/helper colors */
18
28
  --input-label-color: var(--color-label-standard);
@@ -33,6 +43,7 @@
33
43
  --input-border-width-emphasis: 1.4px;
34
44
  --input-border-active-color: var(--color-blue-80);
35
45
  --input-border-success-color: var(--color-blue-80);
46
+ --input-border-table-default-color: transparent;
36
47
  /* error는 Figma 44% alpha */
37
48
  --input-border-error-color: rgba(218, 29, 11, 0.44); // --color-feedback-error
38
49
  --input-border-disabled-color: var(--color-border-standard-cool-gray);
@@ -7,6 +7,7 @@ import type {
7
7
  CalendarOnChange,
8
8
  CalendarValue,
9
9
  } from "../../calendar";
10
+ import type { InputPriority } from "./foundation";
10
11
 
11
12
  /**
12
13
  * Calendar trigger(Input) 영역에서 필요한 속성 묶음.
@@ -17,6 +18,7 @@ import type {
17
18
  * @property {(event: MouseEvent<Element>) => void} [onClick] trigger 클릭 핸들러
18
19
  * @property {boolean} [disabled] disabled 여부
19
20
  * @property {string} [placeholder] trigger 내부 placeholder
21
+ * @property {InputPriority} [priority] trigger input priority
20
22
  */
21
23
  export interface InputCalendarTriggerProps {
22
24
  /**
@@ -43,6 +45,11 @@ export interface InputCalendarTriggerProps {
43
45
  * trigger 내부 placeholder
44
46
  */
45
47
  placeholder?: string;
48
+ /**
49
+ * trigger input priority
50
+ * - table일 때 Trigger는 left icon 배치를 사용한다.
51
+ */
52
+ priority?: InputPriority;
46
53
  }
47
54
 
48
55
  /**
@@ -69,6 +76,7 @@ export interface InputCalendarTriggerViewProps extends InputCalendarTriggerProps
69
76
  * @property {string} [placeholder] 트리거 placeholder
70
77
  * @property {boolean} [disabled] disabled 여부
71
78
  * @property {(event: MouseEvent<Element>) => void} [onClick] 트리거 클릭 핸들러
79
+ * @property {InputPriority} [priority] trigger input priority
72
80
  */
73
81
  export interface InputCalendarTriggerRenderProps {
74
82
  /**
@@ -95,6 +103,11 @@ export interface InputCalendarTriggerRenderProps {
95
103
  * 트리거 클릭 핸들러
96
104
  */
97
105
  onClick?: (event: MouseEvent<Element>) => void;
106
+ /**
107
+ * trigger input priority
108
+ * - table일 때 Trigger는 left icon 배치를 사용한다.
109
+ */
110
+ priority?: InputPriority;
98
111
  }
99
112
 
100
113
  /**
@@ -108,6 +121,7 @@ export interface InputCalendarTriggerRenderProps {
108
121
  * @property {boolean} [readOnly] 읽기 전용 여부
109
122
  * @property {boolean} [disabled] disabled 여부
110
123
  * @property {CalendarDatePickerProps} [datePickerProps] Mantine DatePicker 직접 옵션
124
+ * @property {InputPriority} [priority] trigger input priority
111
125
  * @property {ReactNode} [header] 커스텀 header 콘텐츠
112
126
  * @property {ReactNode} [footer] 커스텀 footer 콘텐츠
113
127
  * @property {unknown} [timePicker] TimePicker 확장용 예약 슬롯(현재 미구현)
@@ -168,6 +182,7 @@ export interface InputCalendarProps extends InputCalendarTriggerProps {
168
182
  timePicker?: unknown;
169
183
  /**
170
184
  * calendar 열림 제어 여부
185
+ * - 지정되면 제어형(open state controlled)으로 동작한다.
171
186
  */
172
187
  calendarOpened?: boolean;
173
188
  /**