@uniai-fe/uds-primitives 0.12.4 → 0.12.6
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/README.md +17 -0
- package/dist/styles.css +2 -2
- package/package.json +17 -17
- package/src/components/form/markup/Provider.tsx +7 -3
- package/src/components/form/markup/form-field/Body.tsx +1 -0
- package/src/components/form/markup/form-field/Container.tsx +3 -1
- package/src/components/form/markup/form-field/Footer.tsx +1 -0
- package/src/components/form/markup/form-field/Header.tsx +2 -0
- package/src/components/form/markup/form-field/Template.tsx +3 -2
- package/src/components/form/markup/form-field/index.tsx +1 -1
- package/src/components/form/markup/index.tsx +1 -0
- package/src/components/form/styles/form-field/variables.scss +2 -2
- package/src/components/form/types/props.ts +21 -19
- package/src/components/form/utils/form-field.ts +6 -4
- package/src/components/input/markup/date/Template.tsx +83 -33
- package/src/components/input/markup/date/Trigger.tsx +28 -12
- package/src/components/input/markup/date/range/Template.tsx +94 -35
- package/src/components/input/markup/time/Template.tsx +118 -5
- package/src/components/input/markup/time/Trigger.tsx +35 -2
- package/src/components/input/types/date.ts +176 -33
- package/src/components/input/types/time.ts +10 -4
- package/src/components/slot/index.tsx +3 -6
- package/src/components/slot/markup/Base.tsx +146 -9
- package/src/components/slot/markup/index.tsx +2 -4
- package/src/components/slot/types/props.ts +119 -23
- package/src/components/table/types/foundation.ts +9 -7
- package/src/components/time-picker/markup/Footer.tsx +5 -5
- package/src/components/time-picker/markup/HourSection.tsx +7 -7
- package/src/components/time-picker/markup/MinuteSection.tsx +5 -5
- package/src/components/time-picker/markup/Summary.tsx +2 -2
- package/src/components/time-picker/markup/Template.tsx +11 -11
- package/src/components/time-picker/types/time-picker.ts +6 -0
- package/src/components/time-picker/utils/time.ts +21 -9
- package/src/index.scss +0 -2
- package/src/index.tsx +8 -2
- package/src/types/index.ts +1 -4
- package/src/utils/index.ts +8 -4
- package/src/utils/selected-values.ts +20 -14
- package/src/components/scrollbar/hooks/index.ts +0 -4
- package/src/components/scrollbar/img/.gitkeep +0 -0
- package/src/components/scrollbar/index.scss +0 -1
- package/src/components/scrollbar/index.tsx +0 -4
- package/src/components/scrollbar/markup/index.tsx +0 -4
- package/src/components/scrollbar/styles/index.scss +0 -0
- package/src/components/scrollbar/types/index.ts +0 -4
- package/src/components/scrollbar/utils/index.ts +0 -4
- package/src/components/spinner/hooks/index.ts +0 -4
- package/src/components/spinner/img/.gitkeep +0 -0
- package/src/components/spinner/index.scss +0 -1
- package/src/components/spinner/index.tsx +0 -4
- package/src/components/spinner/markup/index.tsx +0 -4
- package/src/components/spinner/styles/index.scss +0 -0
- package/src/components/spinner/types/index.ts +0 -4
- package/src/components/spinner/utils/index.ts +0 -4
- package/src/hooks/index.ts +0 -4
package/README.md
CHANGED
|
@@ -46,6 +46,23 @@ Button은 기존 priority 범위를 유지한다.
|
|
|
46
46
|
`Input.Time.Template`과 standalone `TimePicker.Template`, `TimePickerUtils`도 root public
|
|
47
47
|
entry에서 제공한다.
|
|
48
48
|
|
|
49
|
+
Select와 Dropdown이 함께 사용하는 `SelectedValue`, `toSelectedValueKey`,
|
|
50
|
+
`isSameSelectedValue`, `isSameSelectedValueList`, `normalizeSingleSelectedValue`,
|
|
51
|
+
`normalizeSelectedValuesByMode`도 root public entry에서 제공한다.
|
|
52
|
+
|
|
53
|
+
`Slot.Base`는 `as`로 렌더링 요소를 교체하거나, `asChild`로 단일 React element에
|
|
54
|
+
className, style, event handler와 ref를 병합해 wrapper 없이 렌더링한다.
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { Slot } from "@uniai-fe/uds-primitives";
|
|
58
|
+
|
|
59
|
+
<Slot.Base asChild className="card-link">
|
|
60
|
+
<a href="/docs">문서 보기</a>
|
|
61
|
+
</Slot.Base>;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`asChild`에서 anchor/button 같은 element 전용 prop은 child에 둔다.
|
|
65
|
+
|
|
49
66
|
## Styles
|
|
50
67
|
|
|
51
68
|
CSS consumer는 Foundation 다음에 Primitives를 로드한다.
|
package/dist/styles.css
CHANGED
|
@@ -442,8 +442,8 @@
|
|
|
442
442
|
--dropdown-text-weight-selected-xsmall: var(--dropdown-text-weight);
|
|
443
443
|
--form-field-width: auto;
|
|
444
444
|
--form-field-flex: 0 0 auto;
|
|
445
|
-
--form-field-gap-x: var(--spacing-gap-5
|
|
446
|
-
--form-field-gap-y: var(--spacing-gap-3
|
|
445
|
+
--form-field-gap-x: var(--spacing-gap-5);
|
|
446
|
+
--form-field-gap-y: var(--spacing-gap-3);
|
|
447
447
|
--form-field-label-font-size: var(--font-label-small-size);
|
|
448
448
|
--form-field-label-font-weight: 400;
|
|
449
449
|
--form-field-label-line-height: var(--font-label-small-line-height);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniai-fe/uds-primitives",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.6",
|
|
4
4
|
"description": "UNIAI Design System; Primitives Components Package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@mantine/core": ">=8 <9",
|
|
47
47
|
"@mantine/dates": ">=8 <9",
|
|
48
48
|
"@mantine/hooks": ">=8 <9",
|
|
49
|
-
"@uniai-fe/uds-foundation": "^0.4.8 || ^0.5.0",
|
|
49
|
+
"@uniai-fe/uds-foundation": "^0.4.8 || ^0.5.0 || ^0.6.0",
|
|
50
50
|
"@uniai-fe/util-functions": "^0.3.0 || ^0.4.0",
|
|
51
51
|
"react": ">=19 <20",
|
|
52
52
|
"react-daum-postcode": "^4.0.0",
|
|
@@ -63,13 +63,13 @@
|
|
|
63
63
|
"dayjs": "^1.11.20"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@radix-ui/react-checkbox": "^1.3.
|
|
67
|
-
"@radix-ui/react-dropdown-menu": "^2.1.
|
|
68
|
-
"@radix-ui/react-popover": "^1.1.
|
|
69
|
-
"@radix-ui/react-radio-group": "^1.4.
|
|
70
|
-
"@radix-ui/react-tabs": "^1.1.
|
|
71
|
-
"@radix-ui/react-tooltip": "^1.2.
|
|
72
|
-
"@radix-ui/react-visually-hidden": "^1.2.
|
|
66
|
+
"@radix-ui/react-checkbox": "^1.3.11",
|
|
67
|
+
"@radix-ui/react-dropdown-menu": "^2.1.24",
|
|
68
|
+
"@radix-ui/react-popover": "^1.1.23",
|
|
69
|
+
"@radix-ui/react-radio-group": "^1.4.7",
|
|
70
|
+
"@radix-ui/react-tabs": "^1.1.21",
|
|
71
|
+
"@radix-ui/react-tooltip": "^1.2.16",
|
|
72
|
+
"@radix-ui/react-visually-hidden": "^1.2.11",
|
|
73
73
|
"clsx": "^2.1.1",
|
|
74
74
|
"dayjs": "^1.11.21",
|
|
75
75
|
"react-daum-postcode": "^4.0.0"
|
|
@@ -80,18 +80,18 @@
|
|
|
80
80
|
"@mantine/hooks": "^8.3.18",
|
|
81
81
|
"@svgr/webpack": "^8.1.0",
|
|
82
82
|
"@types/node": "^24.13.3",
|
|
83
|
-
"@types/react": "^19.2.
|
|
84
|
-
"@types/react-dom": "^19.2.
|
|
83
|
+
"@types/react": "^19.2.18",
|
|
84
|
+
"@types/react-dom": "^19.2.4",
|
|
85
85
|
"eslint": "^9.39.5",
|
|
86
86
|
"prettier": "^3.9.6",
|
|
87
|
-
"react-hook-form": "^7.
|
|
88
|
-
"sass": "^1.101.
|
|
87
|
+
"react-hook-form": "^7.84.0",
|
|
88
|
+
"sass": "^1.101.7",
|
|
89
89
|
"typescript": "6.0.3",
|
|
90
|
-
"@uniai-fe/eslint-config": "0.4.2",
|
|
91
|
-
"@uniai-fe/uds-foundation": "0.5.0",
|
|
92
90
|
"@uniai-fe/next-devkit": "0.4.0",
|
|
93
|
-
"@uniai-fe/
|
|
94
|
-
"@uniai-fe/
|
|
91
|
+
"@uniai-fe/eslint-config": "0.4.2",
|
|
92
|
+
"@uniai-fe/uds-foundation": "0.6.0",
|
|
93
|
+
"@uniai-fe/util-functions": "0.4.3",
|
|
94
|
+
"@uniai-fe/tsconfig": "0.2.0"
|
|
95
95
|
},
|
|
96
96
|
"scripts": {
|
|
97
97
|
"check:pre-commit": "pnpm --dir ../../.. run check:pre-commit",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect } from "react";
|
|
4
|
-
// import { useEffect } from "react";
|
|
5
4
|
import {
|
|
6
5
|
FormProvider as ReactHookFormProvider,
|
|
7
6
|
useForm,
|
|
@@ -11,7 +10,13 @@ import {
|
|
|
11
10
|
} from "react-hook-form";
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
|
-
* react-hook-form
|
|
13
|
+
* Form Component; react-hook-form context provider
|
|
14
|
+
* @component
|
|
15
|
+
* @param {object} props Form provider props
|
|
16
|
+
* @property {React.ReactNode} props.children provider 하위 콘텐츠
|
|
17
|
+
* @property {UseFormProps<FormDataType>} [props.options] useForm 초기 옵션
|
|
18
|
+
* @property {(params: UseFormReturn<FormDataType>) => void | undefined} [props.callback] 생성된 form methods 전달 callback
|
|
19
|
+
* @desc useForm methods를 생성하고 React Hook Form context로 제공하며 callback이 있으면 effect에서 methods를 전달한다.
|
|
15
20
|
*/
|
|
16
21
|
export default function FormProvider<FormDataType extends FieldValues>({
|
|
17
22
|
children,
|
|
@@ -27,7 +32,6 @@ export default function FormProvider<FormDataType extends FieldValues>({
|
|
|
27
32
|
|
|
28
33
|
useEffect(() => {
|
|
29
34
|
if (typeof callback === "undefined") return;
|
|
30
|
-
// if (typeof callback !== "undefined")
|
|
31
35
|
callback(formMethods);
|
|
32
36
|
}, [callback, formMethods]);
|
|
33
37
|
|
|
@@ -13,10 +13,12 @@ import {
|
|
|
13
13
|
* @component
|
|
14
14
|
* @param {FormFieldContainerProps} props
|
|
15
15
|
* @property {React.ElementType} [as] container 태그 지정 (default: <section />)
|
|
16
|
-
* @property {
|
|
16
|
+
* @property {"full" | "fit" | "fill" | "auto" | number | string} [width] form field 너비 옵션
|
|
17
|
+
* @property {"default" | "error"} [state] visual state
|
|
17
18
|
* @property {string} [className]
|
|
18
19
|
* @property {React.ReactNode} [children]
|
|
19
20
|
* @property {React.HTMLAttributes<HTMLElement>} [containerProps] container 속성
|
|
21
|
+
* @desc width를 data attribute와 CSS variable로 변환하고 visual state를 노출하는 field wrapper다.
|
|
20
22
|
*/
|
|
21
23
|
const FormFieldContainer = forwardRef<HTMLElement, FormFieldContainerProps>(
|
|
22
24
|
(
|
|
@@ -7,6 +7,7 @@ import type { FormFieldFooterProps } from "../../types";
|
|
|
7
7
|
* @param {FormFieldFooterProps} props
|
|
8
8
|
* @property {string} [className]
|
|
9
9
|
* @property {React.ReactNode} [children]
|
|
10
|
+
* @desc helper 또는 action 콘텐츠와 footer DOM 속성을 렌더링한다.
|
|
10
11
|
*/
|
|
11
12
|
export default function FormFieldFooter({
|
|
12
13
|
className,
|
|
@@ -10,11 +10,13 @@ import { Slot } from "../../../slot";
|
|
|
10
10
|
* @param {FormFieldHeaderProps} props
|
|
11
11
|
* @property {string} [className]
|
|
12
12
|
* @property {string} [label] 필드명
|
|
13
|
+
* @property {React.ReactNode} [labelJsx] label보다 우선하는 커스텀 label JSX
|
|
13
14
|
* @property {React.ElementType} [labelAs] 필드 태그 지정 (default: <h5 />)
|
|
14
15
|
* @property {string} [labelId] 필드 아이디 지정
|
|
15
16
|
* @property {boolean} [required] 필수 여부
|
|
16
17
|
* @property {React.ReactNode} [children] 추가 헤더 내용
|
|
17
18
|
* @property {React.HTMLAttributes<HTMLElement>} [labelProps] 추가 라벨 속성
|
|
19
|
+
* @desc labelJsx를 우선 렌더링하고 문자열 label은 Slot.Text로 감싸 필수 표시와 함께 구성한다.
|
|
18
20
|
*/
|
|
19
21
|
export default function FormFieldHeader({
|
|
20
22
|
className,
|
|
@@ -17,10 +17,11 @@ import FormFieldContainer from "./Container";
|
|
|
17
17
|
* @property {React.ReactNode} [footer] 푸터 내용
|
|
18
18
|
* @property {FormFieldHeaderProps} [headerProps] 헤더 속성
|
|
19
19
|
* @property {FormFieldFooterProps} [footerProps] 푸터 속성
|
|
20
|
-
* @property {
|
|
21
|
-
* @property {
|
|
20
|
+
* @property {"full" | "fit" | "fill" | "auto" | number | string} [width] form field 너비 옵션
|
|
21
|
+
* @property {"default" | "error"} [state] visual state
|
|
22
22
|
* @property {React.HTMLAttributes<HTMLElement>} [containerProps] container 속성
|
|
23
23
|
* @property {React.ReactNode} children
|
|
24
|
+
* @desc Header와 Body를 렌더링하고 footer가 있으면 Footer를 추가해 field layout을 구성한다.
|
|
24
25
|
*/
|
|
25
26
|
const FormFieldTemplate = forwardRef<HTMLElement, FormFieldTemplateProps>(
|
|
26
27
|
(
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
--form-field-width: auto;
|
|
3
3
|
--form-field-flex: 0 0 auto;
|
|
4
4
|
|
|
5
|
-
--form-field-gap-x: var(--spacing-gap-5
|
|
6
|
-
--form-field-gap-y: var(--spacing-gap-3
|
|
5
|
+
--form-field-gap-x: var(--spacing-gap-5);
|
|
6
|
+
--form-field-gap-y: var(--spacing-gap-3);
|
|
7
7
|
|
|
8
8
|
--form-field-label-font-size: var(--font-label-small-size);
|
|
9
9
|
--form-field-label-font-weight: 400;
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import type { ElementType } from "react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Form; field container width option
|
|
5
|
-
*
|
|
6
|
-
* - fit: 콘텐츠 너비에 맞춤
|
|
7
|
-
* - fill: flex-grow 1 분배
|
|
8
|
-
* - auto: 기본 width(auto)
|
|
9
|
-
* - number: px 단위 입력, rem으로 환산
|
|
10
|
-
* - string: CSS width 문자열
|
|
4
|
+
* Form Types; field container width option
|
|
5
|
+
* @desc `"full" | "fit" | "fill" | "auto" | number | string`을 허용한다. number는 px 기준 rem으로 환산하고 preset이 아닌 string은 CSS width로 사용한다.
|
|
11
6
|
*/
|
|
12
7
|
export type FormFieldWidth = "full" | "fit" | "fill" | "auto" | number | string;
|
|
13
8
|
|
|
14
9
|
/**
|
|
15
|
-
* Form; field state option
|
|
10
|
+
* Form Types; field state option
|
|
11
|
+
* @desc `"default" | "error"`를 허용하며 field helper의 visual state를 지정한다.
|
|
16
12
|
*/
|
|
17
13
|
export type FormFieldState = "default" | "error";
|
|
18
14
|
|
|
@@ -26,6 +22,7 @@ export type FormFieldState = "default" | "error";
|
|
|
26
22
|
* @property {React.HTMLAttributes<HTMLElement>} [labelProps] label 태그 속성
|
|
27
23
|
* @property {boolean} [required] 필수 표시
|
|
28
24
|
* @property {React.ReactNode} [children] description 등 추가 요소
|
|
25
|
+
* @desc field label, 필수 표시와 header 추가 콘텐츠를 구성한다.
|
|
29
26
|
*/
|
|
30
27
|
export interface FormFieldHeaderProps extends React.HTMLAttributes<HTMLElement> {
|
|
31
28
|
/**
|
|
@@ -33,7 +30,7 @@ export interface FormFieldHeaderProps extends React.HTMLAttributes<HTMLElement>
|
|
|
33
30
|
*/
|
|
34
31
|
className?: string;
|
|
35
32
|
/**
|
|
36
|
-
* label heading
|
|
33
|
+
* label heading 태그를 지정한다.
|
|
37
34
|
*/
|
|
38
35
|
labelAs?: ElementType;
|
|
39
36
|
/**
|
|
@@ -41,11 +38,11 @@ export interface FormFieldHeaderProps extends React.HTMLAttributes<HTMLElement>
|
|
|
41
38
|
*/
|
|
42
39
|
labelId?: string;
|
|
43
40
|
/**
|
|
44
|
-
* 기본 label
|
|
41
|
+
* 기본 label 텍스트를 지정한다.
|
|
45
42
|
*/
|
|
46
43
|
label?: string;
|
|
47
44
|
/**
|
|
48
|
-
* 커스텀 label JSX
|
|
45
|
+
* label보다 우선하는 커스텀 label JSX를 지정한다.
|
|
49
46
|
*/
|
|
50
47
|
labelJsx?: React.ReactNode;
|
|
51
48
|
/**
|
|
@@ -66,6 +63,7 @@ export interface FormFieldHeaderProps extends React.HTMLAttributes<HTMLElement>
|
|
|
66
63
|
* Form.Field Footer props; `Footer.tsx`에서 helper/action 영역을 담당한다.
|
|
67
64
|
* @property {string} [className] footer className
|
|
68
65
|
* @property {React.ReactNode} [children] footer 내용
|
|
66
|
+
* @desc field helper 또는 action을 footer 영역에 렌더링한다.
|
|
69
67
|
*/
|
|
70
68
|
export interface FormFieldFooterProps extends React.HTMLAttributes<HTMLElement> {
|
|
71
69
|
/**
|
|
@@ -81,10 +79,12 @@ export interface FormFieldFooterProps extends React.HTMLAttributes<HTMLElement>
|
|
|
81
79
|
/**
|
|
82
80
|
* Form.Field Container props; `Container.tsx`에서 wrapper와 width 옵션을 제어한다.
|
|
83
81
|
* @property {string} [className] container className
|
|
84
|
-
* @property {
|
|
85
|
-
* @property {
|
|
86
|
-
* @property {
|
|
82
|
+
* @property {ElementType} [as] container 태그
|
|
83
|
+
* @property {"full" | "fit" | "fill" | "auto" | number | string} [width] width preset 또는 custom width
|
|
84
|
+
* @property {"default" | "error"} [state] visual state
|
|
87
85
|
* @property {React.ReactNode} children Body에 전달될 콘텐츠
|
|
86
|
+
* @see React.HTMLAttributes<HTMLElement>
|
|
87
|
+
* @desc semantic wrapper, width와 helper visual state를 제어한다.
|
|
88
88
|
*/
|
|
89
89
|
export interface FormFieldContainerProps extends React.HTMLAttributes<HTMLElement> {
|
|
90
90
|
/**
|
|
@@ -96,11 +96,11 @@ export interface FormFieldContainerProps extends React.HTMLAttributes<HTMLElemen
|
|
|
96
96
|
*/
|
|
97
97
|
as?: ElementType;
|
|
98
98
|
/**
|
|
99
|
-
* width
|
|
99
|
+
* `"full" | "fit" | "fill" | "auto" | number | string` width 옵션을 지정한다.
|
|
100
100
|
*/
|
|
101
101
|
width?: FormFieldWidth;
|
|
102
102
|
/**
|
|
103
|
-
* visual state
|
|
103
|
+
* `"default" | "error"` visual state를 지정한다.
|
|
104
104
|
*/
|
|
105
105
|
state?: FormFieldState;
|
|
106
106
|
/**
|
|
@@ -115,10 +115,12 @@ export interface FormFieldContainerProps extends React.HTMLAttributes<HTMLElemen
|
|
|
115
115
|
* @property {FormFieldHeaderProps} [headerProps] Header props
|
|
116
116
|
* @property {FormFieldFooterProps} [footerProps] Footer props
|
|
117
117
|
* @property {string} [className] container className
|
|
118
|
-
* @property {
|
|
119
|
-
* @property {
|
|
120
|
-
* @property {
|
|
118
|
+
* @property {ElementType} [as] container 태그
|
|
119
|
+
* @property {"full" | "fit" | "fill" | "auto" | number | string} [width] width preset 또는 custom width
|
|
120
|
+
* @property {"default" | "error"} [state] visual state
|
|
121
121
|
* @property {React.ReactNode} children Body에 전달될 콘텐츠
|
|
122
|
+
* @see FormFieldContainerProps
|
|
123
|
+
* @desc Container props를 상속하고 Header, Body와 선택적 Footer를 조합한다.
|
|
122
124
|
*/
|
|
123
125
|
export interface FormFieldTemplateProps extends FormFieldContainerProps {
|
|
124
126
|
/**
|
|
@@ -6,8 +6,9 @@ const WIDTH_PRESETS = new Set<FormFieldWidth>(["full", "fit", "fill", "auto"]);
|
|
|
6
6
|
/**
|
|
7
7
|
* Form.Field width attr helper; preset이면 그대로 반환하고 custom이면 "custom"/기본값은 "auto".
|
|
8
8
|
* @function
|
|
9
|
-
* @param {
|
|
10
|
-
* @
|
|
9
|
+
* @param {"full" | "fit" | "fill" | "auto" | number | string} [width] width preset 또는 custom width
|
|
10
|
+
* @desc Form.Field width를 data-width attribute 값으로 변환한다.
|
|
11
|
+
* @return {string} preset, "custom" 또는 "auto"
|
|
11
12
|
*/
|
|
12
13
|
export const getFormFieldWidthAttr = (width?: FormFieldWidth): string => {
|
|
13
14
|
if (typeof width === "string" && WIDTH_PRESETS.has(width)) {
|
|
@@ -24,8 +25,9 @@ export const getFormFieldWidthAttr = (width?: FormFieldWidth): string => {
|
|
|
24
25
|
/**
|
|
25
26
|
* Form.Field width style helper; number/string custom 값을 CSS width 문자열로 환산한다.
|
|
26
27
|
* @function
|
|
27
|
-
* @param {
|
|
28
|
-
* @
|
|
28
|
+
* @param {"full" | "fit" | "fill" | "auto" | number | string} [width] width preset 또는 custom width
|
|
29
|
+
* @desc 유한한 number는 rem으로 환산하고 preset이 아닌 string은 그대로 반환한다.
|
|
30
|
+
* @return {string | undefined} custom CSS width 또는 preset/미지정일 때 undefined
|
|
29
31
|
*/
|
|
30
32
|
export const getFormFieldWidthValue = (
|
|
31
33
|
width?: FormFieldWidth,
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
ChangeEvent,
|
|
5
|
+
FocusEvent as ReactFocusEvent,
|
|
6
|
+
MouseEvent as ReactMouseEvent,
|
|
7
|
+
} from "react";
|
|
4
8
|
import { forwardRef, useCallback, useMemo, useState } from "react";
|
|
5
9
|
import { useUncontrolled } from "@mantine/hooks";
|
|
6
10
|
import { Calendar } from "../../../calendar";
|
|
@@ -26,30 +30,39 @@ const INPUT_DATE_TABLE_FORMAT = "YY-MM-DD";
|
|
|
26
30
|
* placeholder는 format과 별도 props로 처리되며, format 변경으로 placeholder가 자동 치환되지는 않는다.
|
|
27
31
|
* @component
|
|
28
32
|
* @param {InputCalendarProps} props
|
|
29
|
-
* @
|
|
30
|
-
* @
|
|
31
|
-
* @
|
|
32
|
-
* @
|
|
33
|
-
* @
|
|
34
|
-
* @
|
|
35
|
-
* @
|
|
36
|
-
* @
|
|
37
|
-
* @
|
|
38
|
-
* @
|
|
39
|
-
* @
|
|
40
|
-
* @
|
|
41
|
-
* @
|
|
42
|
-
* @
|
|
43
|
-
* @
|
|
44
|
-
* @
|
|
45
|
-
* @
|
|
46
|
-
* @
|
|
47
|
-
* @
|
|
48
|
-
* @
|
|
49
|
-
* @
|
|
50
|
-
* @
|
|
51
|
-
* @
|
|
52
|
-
* @
|
|
33
|
+
* @property {"date" | "date-time" | "time"} [props.mode="date"] Calendar layout mode. 선택 body는 date로 유지된다.
|
|
34
|
+
* @property {1 | 2} [props.columns=1] 동시 노출 달력 수
|
|
35
|
+
* @property {string | null} [props.value] 제어형 날짜 값
|
|
36
|
+
* @property {string | null} [props.defaultValue] 비제어 초기 날짜 값
|
|
37
|
+
* @property {(value: string | null) => void} [props.onChange] 값 변경 이벤트
|
|
38
|
+
* @property {(value: string | null) => void} [props.onValueChange] onChange alias
|
|
39
|
+
* @property {boolean} [props.readOnly] 읽기 전용 여부
|
|
40
|
+
* @property {boolean} [props.disabled] 비활성화 여부
|
|
41
|
+
* @property {CalendarDatePickerProps} [props.datePickerProps] Mantine DatePicker 옵션
|
|
42
|
+
* @property {string} [props.name] hidden input form name
|
|
43
|
+
* @property {string} [props.form] hidden input form id
|
|
44
|
+
* @property {UseFormRegisterReturn} [props.register] hidden input RHF register
|
|
45
|
+
* @property {boolean} [props.required] hidden input required 여부
|
|
46
|
+
* @property {InputCalendarHiddenInputProps} [props.hiddenInputProps] Template 고정값을 제외한 hidden input native props
|
|
47
|
+
* @property {string} [props.placeholder="YYYY-MM-DD"] placeholder
|
|
48
|
+
* @property {"primary" | "secondary" | "tertiary" | "table"} [props.priority="primary"] trigger input priority
|
|
49
|
+
* @property {"small" | "medium" | "large"} [props.size="medium"] trigger input size
|
|
50
|
+
* @property {"default" | "active" | "focused" | "success" | "error" | "disabled" | "loading"} [props.state="default"] trigger input state
|
|
51
|
+
* @property {boolean} [props.block=false] trigger width 100% 여부
|
|
52
|
+
* @property {"full" | "fit" | "fill" | "auto" | number | string} [props.width] trigger width preset 또는 custom width
|
|
53
|
+
* @property {(event: FocusEvent<HTMLInputElement>) => void} [props.onFocus] trigger focus 핸들러
|
|
54
|
+
* @property {(event: FocusEvent<HTMLInputElement>) => void} [props.onBlur] trigger blur 핸들러
|
|
55
|
+
* @property {ReactNode} [props.header] 패널 header 콘텐츠
|
|
56
|
+
* @property {ReactNode} [props.footer] 패널 footer 콘텐츠
|
|
57
|
+
* @property {InputCalendarTexts} [props.texts] 기본 Date 문구
|
|
58
|
+
* @property {unknown} [props.timePicker] 미구현 compatibility prop
|
|
59
|
+
* @property {boolean} [props.calendarOpened] calendar 열림 제어 상태
|
|
60
|
+
* @property {(open: boolean) => void} [props.onCalendarOpen] calendar 열림 변경 이벤트
|
|
61
|
+
* @property {(event: MouseEvent<Element>) => void} [props.onClick] trigger 클릭 핸들러
|
|
62
|
+
* @property {string} [props.id] trigger id
|
|
63
|
+
* @property {ReactNode} [props.trigger] 커스텀 trigger 슬롯
|
|
64
|
+
* @property {(props: InputCalendarTriggerRenderProps) => ReactNode} [props.renderTrigger] 커스텀 trigger 렌더 함수
|
|
65
|
+
* @desc 날짜 표시 trigger, Calendar panel과 YYYY-MM-DD hidden form storage를 조합한다.
|
|
53
66
|
*/
|
|
54
67
|
const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
|
|
55
68
|
(
|
|
@@ -64,10 +77,18 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
|
|
|
64
77
|
disabled,
|
|
65
78
|
datePickerProps,
|
|
66
79
|
name,
|
|
80
|
+
form,
|
|
67
81
|
register,
|
|
82
|
+
required,
|
|
83
|
+
hiddenInputProps,
|
|
68
84
|
placeholder = "YYYY-MM-DD",
|
|
69
85
|
priority = "primary",
|
|
86
|
+
size = "medium",
|
|
70
87
|
state = "default",
|
|
88
|
+
block = false,
|
|
89
|
+
width,
|
|
90
|
+
onFocus,
|
|
91
|
+
onBlur,
|
|
71
92
|
header,
|
|
72
93
|
footer,
|
|
73
94
|
texts,
|
|
@@ -88,7 +109,7 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
|
|
|
88
109
|
* Input Date Template; Calendar open 상태 변경 전달 함수.
|
|
89
110
|
* 비제어 사용을 위해 내부 상태를 갱신하고, 제어형 사용을 위해 외부 owner에게 next open 값을 전달한다.
|
|
90
111
|
* @param {boolean} nextOpen 다음 calendar open 상태
|
|
91
|
-
* @
|
|
112
|
+
* @return {void}
|
|
92
113
|
*/
|
|
93
114
|
const handleCalendarOpenChange = useCallback(
|
|
94
115
|
(nextOpen: boolean) => {
|
|
@@ -113,7 +134,7 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
|
|
|
113
134
|
* Input Date Template; RHF hidden input 변경 이벤트 동기화 함수.
|
|
114
135
|
* trigger 표시값과 form 저장값을 분리하기 위해 CalendarValue를 직렬화한 synthetic change를 register에 전달한다.
|
|
115
136
|
* @param {CalendarValue} nextValue 다음 calendar 선택 값
|
|
116
|
-
* @
|
|
137
|
+
* @return {void}
|
|
117
138
|
*/
|
|
118
139
|
const emitRegisterChange = useCallback(
|
|
119
140
|
(nextValue: CalendarValue) => {
|
|
@@ -140,7 +161,7 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
|
|
|
140
161
|
* Input Date Template; calendar value 갱신 함수.
|
|
141
162
|
* 내부/제어형 value pipeline을 갱신한 뒤 RHF register 저장값을 같은 CalendarValue 기준으로 동기화한다.
|
|
142
163
|
* @param {CalendarValue} nextValue 다음 calendar 선택 값
|
|
143
|
-
* @
|
|
164
|
+
* @return {void}
|
|
144
165
|
*/
|
|
145
166
|
const updateValue = useCallback(
|
|
146
167
|
(nextValue: CalendarValue) => {
|
|
@@ -164,17 +185,33 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
|
|
|
164
185
|
* Input Date Template; trigger click 위임 함수.
|
|
165
186
|
* Calendar Root의 PopOver toggle은 asChild trigger props가 담당하고, 이 함수는 소비자가 넘긴 trigger onClick만 보존한다.
|
|
166
187
|
* @param {ReactMouseEvent<Element>} event trigger click event
|
|
167
|
-
* @
|
|
188
|
+
* @return {void}
|
|
168
189
|
*/
|
|
169
190
|
const handleTriggerClick = (event: ReactMouseEvent<Element>) => {
|
|
170
191
|
triggerOnClick?.(event);
|
|
171
192
|
};
|
|
172
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Input Date Template; trigger blur와 RHF touched 상태 동기화 함수.
|
|
196
|
+
* 표시 trigger는 form name을 소유하지 않으므로 직렬화된 hidden input 값을 synthetic blur event로 전달한다.
|
|
197
|
+
* @utility
|
|
198
|
+
* @param {ReactFocusEvent<HTMLInputElement>} event trigger blur event
|
|
199
|
+
* @desc 소비자 blur callback과 hidden input register blur를 같은 날짜 값으로 동기화한다.
|
|
200
|
+
* @return {void}
|
|
201
|
+
*/
|
|
202
|
+
const handleTriggerBlur = (event: ReactFocusEvent<HTMLInputElement>) => {
|
|
203
|
+
register?.onBlur({
|
|
204
|
+
target: { name: register.name, value: serializedCalendarValue },
|
|
205
|
+
type: "blur",
|
|
206
|
+
});
|
|
207
|
+
onBlur?.(event);
|
|
208
|
+
};
|
|
209
|
+
|
|
173
210
|
/**
|
|
174
211
|
* Input Date Template; Calendar Core change adapter 함수.
|
|
175
212
|
* Calendar.Root의 onChange 값을 Template value/RHF 동기화 pipeline으로 연결한다.
|
|
176
213
|
* @param {CalendarValue} nextValue 다음 calendar 선택 값
|
|
177
|
-
* @
|
|
214
|
+
* @return {void}
|
|
178
215
|
*/
|
|
179
216
|
const handleCalendarChange = (nextValue: CalendarValue) => {
|
|
180
217
|
updateValue(nextValue);
|
|
@@ -201,7 +238,12 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
|
|
|
201
238
|
readOnly,
|
|
202
239
|
onClick: handleTriggerClick,
|
|
203
240
|
priority,
|
|
241
|
+
size,
|
|
204
242
|
state,
|
|
243
|
+
block,
|
|
244
|
+
width,
|
|
245
|
+
onFocus,
|
|
246
|
+
onBlur: handleTriggerBlur,
|
|
205
247
|
};
|
|
206
248
|
|
|
207
249
|
const triggerNode = trigger ?? renderTrigger?.(triggerRenderProps) ?? (
|
|
@@ -214,6 +256,11 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
|
|
|
214
256
|
readOnly={readOnly}
|
|
215
257
|
onClick={handleTriggerClick}
|
|
216
258
|
priority={priority}
|
|
259
|
+
size={size}
|
|
260
|
+
block={block}
|
|
261
|
+
width={width}
|
|
262
|
+
onFocus={onFocus}
|
|
263
|
+
onBlur={handleTriggerBlur}
|
|
217
264
|
/>
|
|
218
265
|
);
|
|
219
266
|
|
|
@@ -228,7 +275,7 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
|
|
|
228
275
|
readOnly={readOnly}
|
|
229
276
|
value={calendarValue}
|
|
230
277
|
onChange={handleCalendarChange}
|
|
231
|
-
//
|
|
278
|
+
// Mantine DatePicker는 valueFormat prop을 지원하지 않으므로 옵션을 그대로 전달한다.
|
|
232
279
|
datePickerProps={datePickerProps}
|
|
233
280
|
header={header}
|
|
234
281
|
footer={footerContent}
|
|
@@ -239,11 +286,14 @@ const InputDateTemplate = forwardRef<HTMLDivElement, InputCalendarProps>(
|
|
|
239
286
|
</Calendar.Root>
|
|
240
287
|
{(register?.name || name) && (
|
|
241
288
|
<input
|
|
242
|
-
//
|
|
289
|
+
// trigger 표시값과 form 저장값을 분리해 hidden input만 form에 참여한다.
|
|
290
|
+
{...hiddenInputProps}
|
|
291
|
+
{...register}
|
|
243
292
|
type="hidden"
|
|
244
293
|
value={serializedCalendarValue}
|
|
245
294
|
name={register?.name ?? name}
|
|
246
|
-
{
|
|
295
|
+
form={form ?? hiddenInputProps?.form}
|
|
296
|
+
required={required ?? hiddenInputProps?.required}
|
|
247
297
|
/>
|
|
248
298
|
)}
|
|
249
299
|
</>
|
|
@@ -19,17 +19,23 @@ import type { InputCalendarTriggerViewProps } from "../../types";
|
|
|
19
19
|
* 그 외 priority에서는 기존처럼 right 슬롯을 유지한다.
|
|
20
20
|
* @component
|
|
21
21
|
* @param {InputCalendarTriggerViewProps} props
|
|
22
|
-
* @
|
|
23
|
-
* @
|
|
24
|
-
* @
|
|
25
|
-
* @
|
|
26
|
-
* @
|
|
27
|
-
* @
|
|
28
|
-
* @
|
|
29
|
-
* @
|
|
30
|
-
* @
|
|
31
|
-
* @
|
|
32
|
-
* @
|
|
22
|
+
* @property {string} [props.className] trigger className
|
|
23
|
+
* @property {string} [props.placeholder] placeholder
|
|
24
|
+
* @property {string} [props.displayValue] 표시 문자열
|
|
25
|
+
* @property {(event: MouseEvent<Element>) => void} [props.onClick] 클릭 핸들러
|
|
26
|
+
* @property {boolean} [props.disabled] disabled 여부
|
|
27
|
+
* @property {boolean} [props.readOnly] readOnly 여부
|
|
28
|
+
* @property {"primary" | "secondary" | "tertiary" | "table"} [props.priority] trigger input priority
|
|
29
|
+
* @property {"small" | "medium" | "large"} [props.size] trigger input size
|
|
30
|
+
* @property {"default" | "active" | "focused" | "success" | "error" | "disabled" | "loading"} [props.state] trigger input state
|
|
31
|
+
* @property {boolean} [props.block] trigger width 100% 여부
|
|
32
|
+
* @property {"full" | "fit" | "fill" | "auto" | number | string} [props.width] trigger width preset 또는 custom width
|
|
33
|
+
* @property {(event: FocusEvent<HTMLInputElement>) => void} [props.onFocus] trigger focus 핸들러
|
|
34
|
+
* @property {(event: FocusEvent<HTMLInputElement>) => void} [props.onBlur] trigger blur 핸들러
|
|
35
|
+
* @property {string} [props.id] input id
|
|
36
|
+
* @property {string} [props.name] form name
|
|
37
|
+
* @property {UseFormRegisterReturn} [props.register] RHF register
|
|
38
|
+
* @desc Date 값을 직접 편집하지 않고 Calendar 선택값을 표시하는 기본 trigger다.
|
|
33
39
|
*/
|
|
34
40
|
const InputDateTrigger = forwardRef<
|
|
35
41
|
HTMLInputElement,
|
|
@@ -44,7 +50,12 @@ const InputDateTrigger = forwardRef<
|
|
|
44
50
|
disabled,
|
|
45
51
|
readOnly,
|
|
46
52
|
priority,
|
|
53
|
+
size,
|
|
47
54
|
state,
|
|
55
|
+
block,
|
|
56
|
+
width,
|
|
57
|
+
onFocus,
|
|
58
|
+
onBlur,
|
|
48
59
|
id,
|
|
49
60
|
name,
|
|
50
61
|
register,
|
|
@@ -90,7 +101,7 @@ const InputDateTrigger = forwardRef<
|
|
|
90
101
|
triggerInputRef.current?.click();
|
|
91
102
|
};
|
|
92
103
|
|
|
93
|
-
//
|
|
104
|
+
// Date trigger는 직접 타이핑을 막아 달력 선택을 단일 입력 경로로 유지한다.
|
|
94
105
|
const shouldBlockTyping = !disabled && !readOnly;
|
|
95
106
|
|
|
96
107
|
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
@@ -170,9 +181,14 @@ const InputDateTrigger = forwardRef<
|
|
|
170
181
|
name={name ?? register?.name}
|
|
171
182
|
register={register}
|
|
172
183
|
priority={priority}
|
|
184
|
+
size={size}
|
|
173
185
|
state={state}
|
|
186
|
+
block={block}
|
|
187
|
+
width={width}
|
|
174
188
|
placeholder={placeholder}
|
|
175
189
|
className={clsx("input-date-trigger-input", className)}
|
|
190
|
+
onFocus={onFocus}
|
|
191
|
+
onBlur={onBlur}
|
|
176
192
|
onClick={handleInputClick}
|
|
177
193
|
onKeyDown={handleKeyDown}
|
|
178
194
|
onBeforeInput={handleBeforeInput}
|