@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.
- package/dist/styles.css +419 -0
- package/package.json +2 -1
- package/src/components/calendar/types/calendar.ts +5 -0
- package/src/components/dropdown/markup/Template.tsx +41 -17
- package/src/components/dropdown/markup/foundation/Container.tsx +14 -2
- package/src/components/dropdown/markup/foundation/MenuItem.tsx +20 -6
- package/src/components/dropdown/markup/foundation/Root.tsx +8 -1
- package/src/components/dropdown/markup/foundation/Trigger.tsx +7 -1
- package/src/components/dropdown/styles/dropdown.scss +4 -0
- package/src/components/dropdown/types/props.ts +5 -0
- package/src/components/input/markup/date/Template.tsx +36 -5
- package/src/components/input/markup/date/Trigger.tsx +22 -4
- package/src/components/input/markup/foundation/Input.tsx +19 -11
- package/src/components/input/markup/foundation/Utility.tsx +11 -7
- package/src/components/input/styles/date.scss +21 -0
- package/src/components/input/styles/foundation.scss +30 -0
- package/src/components/input/styles/variables.scss +11 -0
- package/src/components/input/types/date.ts +15 -0
- package/src/components/input/types/foundation.ts +18 -11
- package/src/components/input/utils/date.ts +15 -1
- package/src/components/select/hooks/index.ts +1 -45
- package/src/components/select/hooks/interaction.ts +62 -0
- package/src/components/select/markup/Default.tsx +59 -35
- package/src/components/select/markup/foundation/Base.tsx +12 -4
- package/src/components/select/markup/foundation/Container.tsx +37 -34
- package/src/components/select/markup/foundation/Icon.tsx +6 -1
- package/src/components/select/markup/multiple/Multiple.tsx +62 -35
- package/src/components/select/markup/multiple/SelectedChip.tsx +5 -2
- package/src/components/select/styles/select.scss +50 -0
- package/src/components/select/styles/variables.scss +26 -0
- package/src/components/select/types/base.ts +3 -2
- package/src/components/select/types/icon.ts +7 -6
- package/src/components/select/types/index.ts +1 -0
- package/src/components/select/types/interaction.ts +30 -0
- package/src/components/select/types/props.ts +8 -0
- package/src/components/select/types/trigger.ts +4 -0
- package/src/components/table/hooks/index.ts +0 -3
- package/src/components/table/index.tsx +5 -3
- package/src/components/table/markup/Container.tsx +126 -0
- package/src/components/table/markup/foundation/Body.tsx +24 -0
- package/src/components/table/markup/foundation/Cell.tsx +72 -0
- package/src/components/table/markup/foundation/Col.tsx +22 -0
- package/src/components/table/markup/foundation/Colgroup.tsx +29 -0
- package/src/components/table/markup/foundation/Foot.tsx +24 -0
- package/src/components/table/markup/foundation/Head.tsx +24 -0
- package/src/components/table/markup/foundation/Root.tsx +32 -0
- package/src/components/table/markup/foundation/Row.tsx +32 -0
- package/src/components/table/markup/foundation/Td.tsx +37 -0
- package/src/components/table/markup/foundation/Th.tsx +39 -0
- package/src/components/table/markup/foundation/index.tsx +30 -0
- package/src/components/table/markup/index.tsx +8 -2
- package/src/components/table/styles/foundation.scss +247 -0
- package/src/components/table/styles/index.scss +2 -0
- package/src/components/table/styles/variables.scss +29 -0
- package/src/components/table/types/foundation.ts +250 -0
- package/src/components/table/types/index.ts +1 -4
- package/src/components/tooltip/img/info.svg +5 -0
- package/src/components/tooltip/img/information.svg +9 -0
- package/src/components/tooltip/index.scss +1 -0
- package/src/components/tooltip/index.tsx +4 -0
- package/src/components/tooltip/markup/Message.tsx +70 -0
- package/src/components/tooltip/markup/Root.tsx +32 -0
- package/src/components/tooltip/markup/Template.tsx +46 -0
- package/src/components/tooltip/markup/Trigger.tsx +32 -0
- package/src/components/tooltip/markup/index.tsx +18 -0
- package/src/components/tooltip/styles/index.scss +2 -0
- package/src/components/tooltip/styles/tooltip.scss +47 -0
- package/src/components/tooltip/styles/variables.scss +14 -0
- package/src/components/tooltip/types/index.ts +1 -0
- package/src/components/tooltip/types/props.ts +118 -0
- package/src/index.scss +1 -0
- 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
|
|
12
|
+
* Dropdown Foundation; Menu Item 옵션 렌더링 컴포넌트
|
|
13
13
|
* @component
|
|
14
14
|
* @param {DropdownMenuItemProps} props dropdown menu option props
|
|
15
|
-
* @param {
|
|
16
|
-
* @param {
|
|
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
|
-
{
|
|
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
|
|
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
|
|
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) => {
|
|
@@ -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
|
-
() =>
|
|
117
|
-
|
|
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={
|
|
198
|
+
datePickerProps={resolvedDatePickerProps}
|
|
169
199
|
header={header}
|
|
170
200
|
footer={footerContent}
|
|
171
|
-
open={
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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.
|
|
39
|
-
* @param {React.ReactNode} [props.
|
|
40
|
-
* @param {React.ReactNode} [props.
|
|
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
|
-
:
|
|
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
|
-
|
|
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={
|
|
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
|
-
|
|
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={
|
|
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 {
|
|
13
|
-
* @param {React.ReactNode} [props.
|
|
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 {
|
|
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 {
|
|
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 && !
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
/**
|