@uniai-fe/uds-templates 0.3.3 → 0.3.5
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 +7 -5
- package/package.json +8 -7
- package/src/modal/components/alert/Contents.tsx +28 -0
- package/src/modal/components/alert/Template.tsx +59 -0
- package/src/modal/components/core/Body.tsx +23 -0
- package/src/modal/components/core/Container.tsx +59 -0
- package/src/modal/{core/components → components/core}/Root.tsx +12 -10
- package/src/modal/{core/components → components/core}/RouteReset.tsx +4 -1
- package/src/modal/{core/components/Provider.tsx → components/core/StackProvider.tsx} +12 -6
- package/src/modal/{core/components/FooterPositionButton.tsx → components/core/footer/Button.tsx} +13 -4
- package/src/modal/components/core/footer/ButtonGroup.tsx +34 -0
- package/src/modal/{core/components/FooterButtons.tsx → components/core/footer/ButtonWrapper.tsx} +28 -19
- package/src/modal/components/core/header/CloseButton.tsx +32 -0
- package/src/modal/components/core/header/Container.tsx +21 -0
- package/src/modal/components/dialog/Contents.tsx +32 -0
- package/src/modal/{templates/components/DialogHeader.tsx → components/dialog/Header.tsx} +17 -5
- package/src/modal/components/dialog/Template.tsx +86 -0
- package/src/modal/components/index.tsx +27 -0
- package/src/modal/{core/hooks → hooks}/useModal.ts +16 -13
- package/src/modal/index.tsx +12 -13
- package/src/modal/{core/jotai → jotai}/atoms.ts +2 -2
- package/src/modal/styles/container.scss +14 -5
- package/src/modal/types/components.ts +139 -4
- package/src/modal/types/index.ts +7 -1
- package/src/modal/utils/create-alert-footer-buttons.ts +59 -0
- package/src/modal/utils/create-dialog-footer-buttons.ts +72 -0
- package/src/page-frame/desktop/components/header/util/setting/Button.tsx +7 -11
- package/src/modal/core/components/Container.tsx +0 -55
- package/src/modal/core/components/FooterPositionGroup.tsx +0 -29
- package/src/modal/templates/Alert.tsx +0 -115
- package/src/modal/templates/Dialog.tsx +0 -127
- package/src/modal/templates/components/DialogBody.tsx +0 -20
|
@@ -5,10 +5,17 @@ import clsx from "clsx";
|
|
|
5
5
|
import type { DialogHeaderProps } from "../../types";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Dialog
|
|
8
|
+
* Modal Dialog Header; Dialog 헤더 영역
|
|
9
9
|
* @component
|
|
10
|
-
* @param {DialogHeaderProps} props
|
|
11
|
-
* @
|
|
10
|
+
* @param {DialogHeaderProps} props
|
|
11
|
+
* @param {React.ReactNode} props.title 타이틀 콘텐츠
|
|
12
|
+
* @param {React.ReactNode} [props.description] description 콘텐츠
|
|
13
|
+
* @param {"center" | "split"} [props.layout] 헤더 레이아웃
|
|
14
|
+
* @param {React.ReactNode} [props.leadingContent] 타이틀 왼쪽 콘텐츠
|
|
15
|
+
* @param {React.ReactNode} [props.trailingContent] 우측 액션 콘텐츠
|
|
16
|
+
* @param {string} [props.className] 헤더 className
|
|
17
|
+
* @example
|
|
18
|
+
* <DialogHeader title="제목" description="설명" />
|
|
12
19
|
*/
|
|
13
20
|
export function DialogHeader({
|
|
14
21
|
title,
|
|
@@ -20,6 +27,11 @@ export function DialogHeader({
|
|
|
20
27
|
}: DialogHeaderProps) {
|
|
21
28
|
const hasRight = Boolean(trailingContent);
|
|
22
29
|
const hasDescription = Boolean(description);
|
|
30
|
+
// 변경: title/description은 string | number일 때만 래핑한다.
|
|
31
|
+
const shouldWrapTitleAsText = ["string", "number"].includes(typeof title);
|
|
32
|
+
const shouldWrapDescriptionAsText = ["string", "number"].includes(
|
|
33
|
+
typeof description,
|
|
34
|
+
);
|
|
23
35
|
|
|
24
36
|
return (
|
|
25
37
|
<header
|
|
@@ -41,7 +53,7 @@ export function DialogHeader({
|
|
|
41
53
|
</div>
|
|
42
54
|
) : null}
|
|
43
55
|
<div className="uds-modal-dialog-header-title">
|
|
44
|
-
{
|
|
56
|
+
{shouldWrapTitleAsText ? <h3>{title}</h3> : title}
|
|
45
57
|
</div>
|
|
46
58
|
</div>
|
|
47
59
|
{hasRight ? (
|
|
@@ -52,7 +64,7 @@ export function DialogHeader({
|
|
|
52
64
|
</div>
|
|
53
65
|
{description ? (
|
|
54
66
|
<p className="uds-modal-dialog-header-description">
|
|
55
|
-
{
|
|
67
|
+
{shouldWrapDescriptionAsText ? (
|
|
56
68
|
<span>{description}</span>
|
|
57
69
|
) : (
|
|
58
70
|
description
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { DialogHeader } from "./Header";
|
|
4
|
+
import { DialogContents } from "./Contents";
|
|
5
|
+
import {
|
|
6
|
+
createDialogFooterButtons,
|
|
7
|
+
resolveDialogPrimaryButton,
|
|
8
|
+
} from "../../utils/create-dialog-footer-buttons";
|
|
9
|
+
|
|
10
|
+
import type { DialogTemplateOptions, ModalState } from "../../types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Modal Dialog Template; Dialog 모달 상태 생성기
|
|
14
|
+
* @component
|
|
15
|
+
* @param {DialogTemplateOptions} options Dialog 모달 옵션
|
|
16
|
+
* @param {string} options.stackKey 모달 스택 키
|
|
17
|
+
* @param {React.ReactNode} options.title 헤더 타이틀
|
|
18
|
+
* @param {React.ReactNode} [options.description] 헤더 description
|
|
19
|
+
* @param {"center" | "split"} [options.headerLayout] 헤더 레이아웃
|
|
20
|
+
* @param {React.ReactNode} [options.headerLeadingContent] 타이틀 왼쪽 콘텐츠
|
|
21
|
+
* @param {React.ReactNode} [options.headerTrailingContent] 우측 액션 콘텐츠
|
|
22
|
+
* @param {string} [options.headerClassName] 헤더 className
|
|
23
|
+
* @param {React.ReactNode} [options.customHeader] 완전 커스텀 header
|
|
24
|
+
* @param {React.ReactNode} options.content 본문 콘텐츠
|
|
25
|
+
* @param {string} [options.bodyClassName] body wrapper className
|
|
26
|
+
* @param {ModalTemplateButtonSpec} [options.confirm] 확인 버튼 스펙
|
|
27
|
+
* @param {ModalTemplateButtonSpec} [options.cancel] 취소 버튼 스펙
|
|
28
|
+
* @param {React.ReactNode} [options.footer] 커스텀 footer
|
|
29
|
+
* @param {number} [options.showDelay] close 후 제거 지연(ms)
|
|
30
|
+
* @param {number | string} [options.width] 모달 width
|
|
31
|
+
* @param {StyleSpacingType} [options.padding] 모달 body padding
|
|
32
|
+
* @returns {ModalState}
|
|
33
|
+
* @example
|
|
34
|
+
* Modal.Dialog({ stackKey: "sample", title: "제목", content: "본문" })
|
|
35
|
+
*/
|
|
36
|
+
export function createDialogModal({
|
|
37
|
+
stackKey,
|
|
38
|
+
title,
|
|
39
|
+
description,
|
|
40
|
+
headerLayout,
|
|
41
|
+
headerLeadingContent,
|
|
42
|
+
headerTrailingContent,
|
|
43
|
+
headerClassName,
|
|
44
|
+
customHeader,
|
|
45
|
+
content,
|
|
46
|
+
bodyClassName,
|
|
47
|
+
confirm,
|
|
48
|
+
cancel,
|
|
49
|
+
footer,
|
|
50
|
+
showDelay,
|
|
51
|
+
width,
|
|
52
|
+
padding,
|
|
53
|
+
}: DialogTemplateOptions): ModalState {
|
|
54
|
+
const primary = resolveDialogPrimaryButton(confirm);
|
|
55
|
+
|
|
56
|
+
const headerNode =
|
|
57
|
+
customHeader ??
|
|
58
|
+
(title ? (
|
|
59
|
+
<DialogHeader
|
|
60
|
+
title={title}
|
|
61
|
+
description={description}
|
|
62
|
+
layout={headerLayout}
|
|
63
|
+
leadingContent={headerLeadingContent}
|
|
64
|
+
trailingContent={headerTrailingContent}
|
|
65
|
+
className={headerClassName}
|
|
66
|
+
/>
|
|
67
|
+
) : null);
|
|
68
|
+
|
|
69
|
+
const footerButtons = footer
|
|
70
|
+
? undefined
|
|
71
|
+
: createDialogFooterButtons({ stackKey, primary, cancel });
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
stackKey,
|
|
75
|
+
modalProps: {
|
|
76
|
+
show: "init",
|
|
77
|
+
showDelay,
|
|
78
|
+
width,
|
|
79
|
+
padding,
|
|
80
|
+
header: headerNode ?? undefined,
|
|
81
|
+
body: <DialogContents content={content} className={bodyClassName} />,
|
|
82
|
+
footer,
|
|
83
|
+
footerButtons,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ModalProvider, ModalStackProvider } from "./core/StackProvider";
|
|
2
|
+
import { ModalRouteReset } from "./core/RouteReset";
|
|
3
|
+
import { createAlertModal } from "./alert/Template";
|
|
4
|
+
import { createDialogModal } from "./dialog/Template";
|
|
5
|
+
|
|
6
|
+
import { useModal } from "../hooks/useModal";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Modal Namespace; 모달 네임스페이스 집계
|
|
10
|
+
*/
|
|
11
|
+
export const ModalNamespace = {
|
|
12
|
+
Provider: ModalProvider,
|
|
13
|
+
StackProvider: ModalStackProvider,
|
|
14
|
+
RouteReset: ModalRouteReset,
|
|
15
|
+
useModal,
|
|
16
|
+
Alert: createAlertModal,
|
|
17
|
+
Dialog: createDialogModal,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
ModalProvider,
|
|
22
|
+
ModalStackProvider,
|
|
23
|
+
ModalRouteReset,
|
|
24
|
+
useModal,
|
|
25
|
+
createAlertModal,
|
|
26
|
+
createDialogModal,
|
|
27
|
+
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useState } from "react";
|
|
4
|
-
import { useAtom } from "jotai";
|
|
5
4
|
|
|
6
|
-
import {
|
|
5
|
+
import { useAtom } from "jotai";
|
|
7
6
|
|
|
8
7
|
import type {
|
|
9
8
|
CloseFlagState,
|
|
@@ -11,7 +10,8 @@ import type {
|
|
|
11
10
|
ModalState,
|
|
12
11
|
ModalStatePatch,
|
|
13
12
|
UseModalReturn,
|
|
14
|
-
} from "
|
|
13
|
+
} from "../types";
|
|
14
|
+
import { modalStackAtom } from "../jotai/atoms";
|
|
15
15
|
|
|
16
16
|
const DEFAULT_CLOSE_DELAY = 400;
|
|
17
17
|
|
|
@@ -19,14 +19,11 @@ const ensureDelay = (value?: number): number =>
|
|
|
19
19
|
typeof value === "number" ? value : DEFAULT_CLOSE_DELAY;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* ui-legacy
|
|
22
|
+
* Modal Hook; ui-legacy 스택 정책 유지 훅
|
|
23
23
|
* @hook
|
|
24
|
-
* @returns {UseModalReturn} 스택
|
|
25
|
-
* @
|
|
26
|
-
*
|
|
27
|
-
* - newModal: stackKey 중복 검증 후 "init" 상태로 푸시
|
|
28
|
-
* - updateModal: 부분 업데이트(클래스/슬롯 변경)
|
|
29
|
-
* - closeModal: show=false → delay 후 제거
|
|
24
|
+
* @returns {UseModalReturn} 모달 스택 상태/조작자
|
|
25
|
+
* @example
|
|
26
|
+
* const { newModal, closeModal } = useModal();
|
|
30
27
|
*/
|
|
31
28
|
export function useModal(): UseModalReturn {
|
|
32
29
|
const [modalStacks, updateModalStack] = useAtom(modalStackAtom);
|
|
@@ -60,7 +57,9 @@ export function useModal(): UseModalReturn {
|
|
|
60
57
|
(nextStack: ModalStatePatch) => {
|
|
61
58
|
updateModalStack((stacks: ModalState[]) =>
|
|
62
59
|
stacks.map(stack => {
|
|
63
|
-
if (stack.stackKey !== nextStack.stackKey)
|
|
60
|
+
if (stack.stackKey !== nextStack.stackKey) {
|
|
61
|
+
return stack;
|
|
62
|
+
}
|
|
64
63
|
const mergedProps = nextStack.modalProps
|
|
65
64
|
? { ...stack.modalProps, ...nextStack.modalProps }
|
|
66
65
|
: stack.modalProps;
|
|
@@ -84,7 +83,9 @@ export function useModal(): UseModalReturn {
|
|
|
84
83
|
|
|
85
84
|
updateModalStack((stacks: ModalState[]) =>
|
|
86
85
|
stacks.map(stack => {
|
|
87
|
-
if (stack.stackKey !== stackKey)
|
|
86
|
+
if (stack.stackKey !== stackKey) {
|
|
87
|
+
return stack;
|
|
88
|
+
}
|
|
88
89
|
hasTarget = true;
|
|
89
90
|
resolvedDelay = ensureDelay(stack.modalProps.showDelay);
|
|
90
91
|
return {
|
|
@@ -107,7 +108,9 @@ export function useModal(): UseModalReturn {
|
|
|
107
108
|
);
|
|
108
109
|
|
|
109
110
|
useEffect(() => {
|
|
110
|
-
if (!closeFlag)
|
|
111
|
+
if (!closeFlag) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
111
114
|
|
|
112
115
|
const timer = setTimeout(() => {
|
|
113
116
|
updateModalStack((stacks: ModalState[]) =>
|
package/src/modal/index.tsx
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import "./index.scss";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export const Modal = {
|
|
11
|
-
Provider: ModalProvider,
|
|
12
|
-
RouteReset: ModalRouteReset,
|
|
3
|
+
import { modalStackAtom } from "./jotai/atoms";
|
|
4
|
+
import {
|
|
5
|
+
ModalNamespace,
|
|
6
|
+
ModalProvider,
|
|
7
|
+
ModalStackProvider,
|
|
8
|
+
ModalRouteReset,
|
|
13
9
|
useModal,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
};
|
|
10
|
+
createAlertModal,
|
|
11
|
+
createDialogModal,
|
|
12
|
+
} from "./components";
|
|
13
|
+
|
|
14
|
+
export const Modal = ModalNamespace;
|
|
17
15
|
|
|
18
16
|
export {
|
|
19
17
|
ModalProvider,
|
|
18
|
+
ModalStackProvider,
|
|
20
19
|
ModalRouteReset,
|
|
21
20
|
useModal,
|
|
22
21
|
createAlertModal,
|
|
@@ -116,7 +116,10 @@
|
|
|
116
116
|
|
|
117
117
|
color: var(--modal-alert-body-color);
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
// 변경: alert 본문은 직계 + class 없는 텍스트 태그에만 기본 타이포를 적용한다.
|
|
120
|
+
// 복합 모듈(children) 내부의 텍스트 스타일 오염을 방지하기 위함이다.
|
|
121
|
+
> :where(p, span, strong, em):not([class]) {
|
|
122
|
+
margin: 0;
|
|
120
123
|
font-size: var(--modal-alert-body-font-size);
|
|
121
124
|
line-height: 1.5em;
|
|
122
125
|
font-weight: var(--font-body-small-weight, 400);
|
|
@@ -165,7 +168,8 @@
|
|
|
165
168
|
align-items: center;
|
|
166
169
|
gap: var(--spacing-gap-1, 4px);
|
|
167
170
|
|
|
168
|
-
|
|
171
|
+
// 변경: header leading slot은 class 없는 직계 텍스트만 기본 타이포를 적용한다.
|
|
172
|
+
> :where(p, span, strong, em):not([class]) {
|
|
169
173
|
color: var(--modal-dialog-title-color);
|
|
170
174
|
font-size: var(--modal-dialog-body-font-size);
|
|
171
175
|
line-height: 1.4em;
|
|
@@ -178,7 +182,8 @@
|
|
|
178
182
|
justify-content: center;
|
|
179
183
|
text-align: center;
|
|
180
184
|
|
|
181
|
-
|
|
185
|
+
// 변경: className이 있는 헤더 텍스트 요소는 모듈 자체 스타일을 우선한다.
|
|
186
|
+
> :where(h1, h2, h3, h4, h5, h6, p, span, strong, em):not([class]) {
|
|
182
187
|
margin: 0;
|
|
183
188
|
color: var(--modal-dialog-title-color);
|
|
184
189
|
font-size: var(--modal-dialog-title-font-size);
|
|
@@ -197,7 +202,8 @@
|
|
|
197
202
|
margin: 0;
|
|
198
203
|
text-align: inherit;
|
|
199
204
|
|
|
200
|
-
|
|
205
|
+
// 변경: description도 class 없는 직계 텍스트에만 기본 스타일을 적용한다.
|
|
206
|
+
> :where(p, span, strong, em):not([class]) {
|
|
201
207
|
margin: 0;
|
|
202
208
|
color: var(--modal-dialog-body-color);
|
|
203
209
|
font-size: var(--modal-dialog-body-font-size);
|
|
@@ -227,7 +233,10 @@
|
|
|
227
233
|
word-break: keep-all;
|
|
228
234
|
color: var(--modal-dialog-body-color);
|
|
229
235
|
|
|
230
|
-
|
|
236
|
+
// 변경: body 전체 하위 요소가 아닌 직계 텍스트 요소만 기본 타이포를 적용해
|
|
237
|
+
// 내부 모듈 컴포넌트의 span/p 스타일 오염을 방지한다.
|
|
238
|
+
> :where(p, span, strong, em):not([class]) {
|
|
239
|
+
margin: 0;
|
|
231
240
|
font-size: var(--modal-dialog-body-font-size);
|
|
232
241
|
line-height: 1.5em;
|
|
233
242
|
font-weight: var(--font-body-small-weight, 400);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
2
|
import type {
|
|
3
3
|
ModalFooterButtonAppearance,
|
|
4
4
|
ModalFooterButtonDefaultOptions,
|
|
@@ -6,7 +6,8 @@ import type {
|
|
|
6
6
|
ModalFooterButtonWidth,
|
|
7
7
|
} from "./footer";
|
|
8
8
|
import type { ModalDialogHeaderLayout } from "./templates";
|
|
9
|
-
import type { ModalProps, ModalState } from "./state";
|
|
9
|
+
import type { ModalProps, ModalState, ModalSections } from "./state";
|
|
10
|
+
import type { ModalFooterButton } from "./footer";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* ModalRoot 컴포넌트 props.
|
|
@@ -34,6 +35,88 @@ export type ModalRootProps = ModalState & {
|
|
|
34
35
|
className?: string;
|
|
35
36
|
};
|
|
36
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Modal Container props.
|
|
40
|
+
* @property {string} stackKey 모달 스택 식별자
|
|
41
|
+
* @property {ReactNode} [header] 헤더 콘텐츠
|
|
42
|
+
* @property {ReactNode} body 본문 콘텐츠
|
|
43
|
+
* @property {ReactNode} [footer] footer 콘텐츠
|
|
44
|
+
* @property {ModalFooterButton[]} [footerButtons] footer 버튼 목록
|
|
45
|
+
* @property {string} [className] 컨테이너 className
|
|
46
|
+
*/
|
|
47
|
+
export type ModalContainerProps = ModalSections & {
|
|
48
|
+
/**
|
|
49
|
+
* 모달 스택 식별자
|
|
50
|
+
*/
|
|
51
|
+
stackKey: string;
|
|
52
|
+
/**
|
|
53
|
+
* footer 버튼 목록
|
|
54
|
+
*/
|
|
55
|
+
footerButtons?: ModalFooterButton[];
|
|
56
|
+
/**
|
|
57
|
+
* 컨테이너 className
|
|
58
|
+
*/
|
|
59
|
+
className?: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Modal Body props.
|
|
64
|
+
* @property {ReactNode} children body 내부 콘텐츠
|
|
65
|
+
* @property {CSSProperties} [style] 런타임 주입 스타일
|
|
66
|
+
* @property {string} [className] body className
|
|
67
|
+
*/
|
|
68
|
+
export type ModalBodyProps = {
|
|
69
|
+
/**
|
|
70
|
+
* body 내부 콘텐츠
|
|
71
|
+
*/
|
|
72
|
+
children: ReactNode;
|
|
73
|
+
/**
|
|
74
|
+
* 런타임 주입 스타일
|
|
75
|
+
*/
|
|
76
|
+
style?: CSSProperties;
|
|
77
|
+
/**
|
|
78
|
+
* body className
|
|
79
|
+
*/
|
|
80
|
+
className?: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Modal Header Container props.
|
|
85
|
+
* @property {ReactNode} children 헤더 콘텐츠
|
|
86
|
+
* @property {string} [className] 헤더 wrapper className
|
|
87
|
+
*/
|
|
88
|
+
export type ModalHeaderContainerProps = {
|
|
89
|
+
/**
|
|
90
|
+
* 헤더 콘텐츠
|
|
91
|
+
*/
|
|
92
|
+
children: ReactNode;
|
|
93
|
+
/**
|
|
94
|
+
* 헤더 wrapper className
|
|
95
|
+
*/
|
|
96
|
+
className?: string;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Modal Header CloseButton props.
|
|
101
|
+
* @property {() => void} onClick 클릭 핸들러
|
|
102
|
+
* @property {string} [ariaLabel] 접근성 라벨
|
|
103
|
+
* @property {string} [className] 버튼 className
|
|
104
|
+
*/
|
|
105
|
+
export type ModalHeaderCloseButtonProps = {
|
|
106
|
+
/**
|
|
107
|
+
* 클릭 핸들러
|
|
108
|
+
*/
|
|
109
|
+
onClick: () => void;
|
|
110
|
+
/**
|
|
111
|
+
* 접근성 라벨
|
|
112
|
+
*/
|
|
113
|
+
ariaLabel?: string;
|
|
114
|
+
/**
|
|
115
|
+
* 버튼 className
|
|
116
|
+
*/
|
|
117
|
+
className?: string;
|
|
118
|
+
};
|
|
119
|
+
|
|
37
120
|
/**
|
|
38
121
|
* footer 위치 정의.
|
|
39
122
|
* @typedef {"left" | "center" | "right"} FooterPosition
|
|
@@ -90,6 +173,7 @@ export type FooterResolvedButton = {
|
|
|
90
173
|
* FooterPositionGroup props.
|
|
91
174
|
* @property {FooterPosition} position 렌더링 위치
|
|
92
175
|
* @property {FooterResolvedButton[]} buttons 위치별 버튼 목록
|
|
176
|
+
* @property {string} [className] 그룹 className
|
|
93
177
|
*/
|
|
94
178
|
export type FooterPositionGroupProps = {
|
|
95
179
|
/**
|
|
@@ -100,17 +184,63 @@ export type FooterPositionGroupProps = {
|
|
|
100
184
|
* 위치별 버튼 목록
|
|
101
185
|
*/
|
|
102
186
|
buttons: FooterResolvedButton[];
|
|
187
|
+
/**
|
|
188
|
+
* 그룹 className
|
|
189
|
+
*/
|
|
190
|
+
className?: string;
|
|
103
191
|
};
|
|
104
192
|
|
|
105
193
|
/**
|
|
106
194
|
* FooterPositionButton props.
|
|
107
195
|
* @property {FooterResolvedButton} button 렌더링할 버튼
|
|
196
|
+
* @property {string} [className] 버튼 className
|
|
108
197
|
*/
|
|
109
198
|
export type FooterPositionButtonProps = {
|
|
110
199
|
/**
|
|
111
200
|
* 렌더링할 버튼
|
|
112
201
|
*/
|
|
113
202
|
button: FooterResolvedButton;
|
|
203
|
+
/**
|
|
204
|
+
* 버튼 className
|
|
205
|
+
*/
|
|
206
|
+
className?: string;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Footer 버튼 래퍼 props.
|
|
211
|
+
* @property {string} stackKey 버튼이 속한 모달 스택 키
|
|
212
|
+
* @property {ModalFooterButton[]} buttons footer 버튼 정의 목록
|
|
213
|
+
* @property {string} [className] wrapper className
|
|
214
|
+
*/
|
|
215
|
+
export type FooterButtonWrapperProps = {
|
|
216
|
+
/**
|
|
217
|
+
* 버튼이 속한 모달 스택 키
|
|
218
|
+
*/
|
|
219
|
+
stackKey: string;
|
|
220
|
+
/**
|
|
221
|
+
* footer 버튼 정의 목록
|
|
222
|
+
*/
|
|
223
|
+
buttons: ModalFooterButton[];
|
|
224
|
+
/**
|
|
225
|
+
* wrapper className
|
|
226
|
+
*/
|
|
227
|
+
className?: string;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* AlertContents props.
|
|
232
|
+
* @property {ReactNode} message alert 본문 메시지
|
|
233
|
+
* @property {string} [className] 콘텐츠 wrapper className
|
|
234
|
+
*/
|
|
235
|
+
export type AlertContentsProps = {
|
|
236
|
+
/**
|
|
237
|
+
* alert 본문 메시지
|
|
238
|
+
*/
|
|
239
|
+
message: ReactNode;
|
|
240
|
+
/**
|
|
241
|
+
* 콘텐츠 wrapper className
|
|
242
|
+
*/
|
|
243
|
+
className?: string;
|
|
114
244
|
};
|
|
115
245
|
|
|
116
246
|
/**
|
|
@@ -150,11 +280,12 @@ export interface DialogHeaderProps {
|
|
|
150
280
|
}
|
|
151
281
|
|
|
152
282
|
/**
|
|
153
|
-
*
|
|
283
|
+
* DialogContents props.
|
|
154
284
|
* @property {ReactNode} content 본문 콘텐츠
|
|
155
285
|
* @property {string} [className] wrapper className
|
|
286
|
+
* @property {string} [contentClassName] 본문 콘텐츠 className
|
|
156
287
|
*/
|
|
157
|
-
export interface
|
|
288
|
+
export interface DialogContentsProps {
|
|
158
289
|
/**
|
|
159
290
|
* 본문 콘텐츠
|
|
160
291
|
*/
|
|
@@ -163,4 +294,8 @@ export interface DialogBodyProps {
|
|
|
163
294
|
* wrapper className
|
|
164
295
|
*/
|
|
165
296
|
className?: string;
|
|
297
|
+
/**
|
|
298
|
+
* 본문 콘텐츠 className
|
|
299
|
+
*/
|
|
300
|
+
contentClassName?: string;
|
|
166
301
|
}
|
package/src/modal/types/index.ts
CHANGED
|
@@ -24,10 +24,16 @@ export type {
|
|
|
24
24
|
export type { CloseFlagState, UseModalReturn } from "./hooks";
|
|
25
25
|
export type {
|
|
26
26
|
ModalRootProps,
|
|
27
|
+
ModalContainerProps,
|
|
28
|
+
ModalBodyProps,
|
|
29
|
+
ModalHeaderContainerProps,
|
|
30
|
+
ModalHeaderCloseButtonProps,
|
|
27
31
|
FooterPosition,
|
|
28
32
|
FooterResolvedButton,
|
|
33
|
+
FooterButtonWrapperProps,
|
|
29
34
|
FooterPositionGroupProps,
|
|
30
35
|
FooterPositionButtonProps,
|
|
36
|
+
AlertContentsProps,
|
|
31
37
|
DialogHeaderProps,
|
|
32
|
-
|
|
38
|
+
DialogContentsProps,
|
|
33
39
|
} from "./components";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ModalFooterButton, ModalTemplateButtonSpec } from "../types";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_CONFIRM_LABEL = "확인";
|
|
4
|
+
const DEFAULT_CANCEL_LABEL = "취소";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Modal Alert Footer Buttons; Alert footer 버튼 스펙 생성
|
|
8
|
+
* @param {string} stackKey 모달 스택 키
|
|
9
|
+
* @param {ModalTemplateButtonSpec} primary 확인 버튼 스펙
|
|
10
|
+
* @param {ModalTemplateButtonSpec} [cancel] 취소 버튼 스펙
|
|
11
|
+
* @returns {ModalFooterButton[]}
|
|
12
|
+
*/
|
|
13
|
+
export const createAlertFooterButtons = ({
|
|
14
|
+
stackKey,
|
|
15
|
+
primary,
|
|
16
|
+
cancel,
|
|
17
|
+
}: {
|
|
18
|
+
stackKey: string;
|
|
19
|
+
primary: ModalTemplateButtonSpec;
|
|
20
|
+
cancel?: ModalTemplateButtonSpec;
|
|
21
|
+
}): ModalFooterButton[] => {
|
|
22
|
+
const buttons: ModalFooterButton[] = [];
|
|
23
|
+
|
|
24
|
+
if (cancel) {
|
|
25
|
+
buttons.push({
|
|
26
|
+
stackKey,
|
|
27
|
+
role: "close",
|
|
28
|
+
position: cancel.position ?? "center",
|
|
29
|
+
width: cancel.width === "full" || !cancel.width ? "fill" : cancel.width,
|
|
30
|
+
defaultOptions: {
|
|
31
|
+
label: cancel.label ?? DEFAULT_CANCEL_LABEL,
|
|
32
|
+
appearance: "text",
|
|
33
|
+
priority: "tertiary",
|
|
34
|
+
textSize: "large",
|
|
35
|
+
disabled: cancel.disabled,
|
|
36
|
+
onClick: cancel.onClick,
|
|
37
|
+
},
|
|
38
|
+
closeOnClick: cancel.closeOnClick,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
buttons.push({
|
|
43
|
+
stackKey,
|
|
44
|
+
role: "close",
|
|
45
|
+
position: primary.position ?? "center",
|
|
46
|
+
width: primary.width === "full" || !primary.width ? "fill" : primary.width,
|
|
47
|
+
defaultOptions: {
|
|
48
|
+
label: primary.label ?? DEFAULT_CONFIRM_LABEL,
|
|
49
|
+
appearance: "text",
|
|
50
|
+
priority: "secondary",
|
|
51
|
+
textSize: "large",
|
|
52
|
+
disabled: primary.disabled,
|
|
53
|
+
onClick: primary.onClick,
|
|
54
|
+
},
|
|
55
|
+
closeOnClick: primary.closeOnClick,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return buttons;
|
|
59
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DialogTemplateOptions,
|
|
3
|
+
ModalFooterButton,
|
|
4
|
+
ModalTemplateButtonSpec,
|
|
5
|
+
} from "../types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Modal Dialog Footer Buttons; Dialog footer 버튼 스펙 생성
|
|
9
|
+
* @param {string} stackKey 모달 스택 키
|
|
10
|
+
* @param {ModalTemplateButtonSpec} primary 확인 버튼 스펙
|
|
11
|
+
* @param {ModalTemplateButtonSpec} [cancel] 취소 버튼 스펙
|
|
12
|
+
* @returns {ModalFooterButton[]}
|
|
13
|
+
*/
|
|
14
|
+
export const createDialogFooterButtons = ({
|
|
15
|
+
stackKey,
|
|
16
|
+
primary,
|
|
17
|
+
cancel,
|
|
18
|
+
}: {
|
|
19
|
+
stackKey: string;
|
|
20
|
+
primary: ModalTemplateButtonSpec;
|
|
21
|
+
cancel?: ModalTemplateButtonSpec;
|
|
22
|
+
}): ModalFooterButton[] => {
|
|
23
|
+
const buttons: ModalFooterButton[] = [];
|
|
24
|
+
|
|
25
|
+
if (cancel) {
|
|
26
|
+
buttons.push({
|
|
27
|
+
stackKey,
|
|
28
|
+
role: "close",
|
|
29
|
+
position: cancel.position,
|
|
30
|
+
width: cancel.width,
|
|
31
|
+
defaultOptions: {
|
|
32
|
+
label: cancel.label ?? "취소",
|
|
33
|
+
fill: "outlined",
|
|
34
|
+
size: "large",
|
|
35
|
+
priority: "secondary",
|
|
36
|
+
disabled: cancel.disabled,
|
|
37
|
+
onClick: cancel.onClick,
|
|
38
|
+
},
|
|
39
|
+
closeOnClick: cancel.closeOnClick,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
buttons.push({
|
|
44
|
+
stackKey,
|
|
45
|
+
role: "close",
|
|
46
|
+
position: primary.position,
|
|
47
|
+
width: primary.width,
|
|
48
|
+
defaultOptions: {
|
|
49
|
+
label: primary.label ?? "확인",
|
|
50
|
+
fill: "solid",
|
|
51
|
+
size: "large",
|
|
52
|
+
priority: "primary",
|
|
53
|
+
disabled: primary.disabled,
|
|
54
|
+
onClick: primary.onClick,
|
|
55
|
+
},
|
|
56
|
+
closeOnClick: primary.closeOnClick,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return buttons;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Modal Dialog Primary Button; Dialog 기본 확인 버튼 스펙 생성
|
|
64
|
+
* @param {DialogTemplateOptions["confirm"]} confirm 확인 버튼 옵션
|
|
65
|
+
* @returns {ModalTemplateButtonSpec}
|
|
66
|
+
*/
|
|
67
|
+
export const resolveDialogPrimaryButton = (
|
|
68
|
+
confirm: DialogTemplateOptions["confirm"],
|
|
69
|
+
): ModalTemplateButtonSpec =>
|
|
70
|
+
confirm ?? {
|
|
71
|
+
label: "확인",
|
|
72
|
+
};
|