@uniai-fe/uds-templates 0.3.14 → 0.3.15
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 +3 -1
- package/package.json +1 -1
- package/src/modal/components/alert/Template.tsx +4 -0
- package/src/modal/components/core/Root.tsx +4 -1
- package/src/modal/components/dialog/Template.tsx +4 -0
- package/src/modal/hooks/useModal.ts +4 -1
- package/src/modal/types/hooks.ts +5 -0
- package/src/modal/types/state.ts +12 -0
- package/src/modal/types/templates.ts +14 -0
package/README.md
CHANGED
|
@@ -142,7 +142,7 @@ ui-legacy에서 사용하던 모달 스택/옵션을 templates 레이어로 옮
|
|
|
142
142
|
|
|
143
143
|
1. **Provider 1회 장착** — 서비스 레이아웃에서 `<Modal.Provider />`를 렌더합니다.
|
|
144
144
|
2. **Route Reset 장착** — `<Modal.RouteReset />`을 Provider와 같은 레벨에서 렌더해 경로 변경 시 스택을 초기화합니다.
|
|
145
|
-
3. **훅 사용** — `const { newModal, closeModal } = Modal.useModal();`
|
|
145
|
+
3. **훅 사용** — `const { newModal, closeModal, hasBlockingModal } = Modal.useModal();`
|
|
146
146
|
4. **템플릿 팩토리** — `Modal.Alert`, `Modal.Dialog`이 `ModalState` 객체를 반환하므로 `newModal(...)`에 그대로 전달합니다.
|
|
147
147
|
|
|
148
148
|
```tsx
|
|
@@ -211,6 +211,8 @@ export function ExampleActions() {
|
|
|
211
211
|
```
|
|
212
212
|
|
|
213
213
|
- footer 버튼은 ui-legacy와 동일하게 `stackKey`, `role`, `defaultOptions/linkOptions` 구조로 관리하며, Alert(Text)/Dialog(Solid) 규격은 templates 내부에서 보장합니다.
|
|
214
|
+
- `closeOnOutsideClick?: boolean`으로 overlay 클릭 닫힘을 제어할 수 있습니다. 기본값은 `true`입니다.
|
|
215
|
+
- `preventRouteChange?: boolean`은 현재 열린 modal 중 route 변경 확인이 필요한 modal이 있는지 집계할 때 사용합니다. 서비스 앱은 `hasBlockingModal`을 읽어 공통 confirm UX를 연결할 수 있습니다.
|
|
214
216
|
- Route Reset을 누락하면 이전 라우트의 모달 스택이 그대로 남으므로, layout 수준에서 Provider와 함께 반드시 렌더합니다.
|
|
215
217
|
- 세부 가드레일·확장 기록은 `CONTEXT-MODAL.md`를 참고하고, 새 템플릿을 추가할 때 해당 문서를 선행 업데이트합니다.
|
|
216
218
|
|
package/package.json
CHANGED
|
@@ -38,6 +38,8 @@ export function createAlertModal<FormContext extends FieldValues>({
|
|
|
38
38
|
showDelay,
|
|
39
39
|
width,
|
|
40
40
|
padding,
|
|
41
|
+
closeOnOutsideClick,
|
|
42
|
+
preventRouteChange,
|
|
41
43
|
formContextOptions,
|
|
42
44
|
}: AlertTemplateOptions<FormContext>): ModalState<FormContext> {
|
|
43
45
|
const primary: ModalTemplateButtonSpec = confirm ?? {
|
|
@@ -51,6 +53,8 @@ export function createAlertModal<FormContext extends FieldValues>({
|
|
|
51
53
|
showDelay,
|
|
52
54
|
width,
|
|
53
55
|
padding,
|
|
56
|
+
closeOnOutsideClick,
|
|
57
|
+
preventRouteChange,
|
|
54
58
|
// 변경 설명: 템플릿 생성 시점의 FormContext 제네릭을 modalProps로 전달한다.
|
|
55
59
|
formContextOptions,
|
|
56
60
|
body: <AlertContents message={message} />,
|
|
@@ -39,8 +39,11 @@ export function ModalRoot({
|
|
|
39
39
|
}, [modalProps.show, stackKey, updateModal]);
|
|
40
40
|
|
|
41
41
|
const handleClose = useCallback(() => {
|
|
42
|
+
if (modalProps.closeOnOutsideClick === false) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
42
45
|
closeModal({ stackKey });
|
|
43
|
-
}, [closeModal, stackKey]);
|
|
46
|
+
}, [closeModal, modalProps.closeOnOutsideClick, stackKey]);
|
|
44
47
|
|
|
45
48
|
const stopPropagation = useCallback(
|
|
46
49
|
(event: React.MouseEvent<HTMLDivElement>) => {
|
|
@@ -78,6 +78,8 @@ export function createDialogModal<FormContext extends FieldValues>({
|
|
|
78
78
|
showDelay,
|
|
79
79
|
width,
|
|
80
80
|
padding,
|
|
81
|
+
closeOnOutsideClick,
|
|
82
|
+
preventRouteChange,
|
|
81
83
|
formContextOptions,
|
|
82
84
|
}: DialogTemplateOptions<FormContext>): ModalState<FormContext> {
|
|
83
85
|
const primary = resolveDialogPrimaryButton(confirm);
|
|
@@ -122,6 +124,8 @@ export function createDialogModal<FormContext extends FieldValues>({
|
|
|
122
124
|
showDelay,
|
|
123
125
|
width,
|
|
124
126
|
padding,
|
|
127
|
+
closeOnOutsideClick,
|
|
128
|
+
preventRouteChange,
|
|
125
129
|
// 변경 설명: 템플릿 생성 시점의 FormContext 제네릭을 modalProps로 전달한다.
|
|
126
130
|
formContextOptions,
|
|
127
131
|
header: headerNode ?? undefined,
|
|
@@ -29,6 +29,9 @@ const ensureDelay = (value?: number): number =>
|
|
|
29
29
|
export function useModal(): UseModalReturn {
|
|
30
30
|
const [modalStacks, updateModalStack] = useAtom(modalStackAtom);
|
|
31
31
|
const modalStacksRef = useRef(modalStacks);
|
|
32
|
+
const hasBlockingModal = modalStacks.some(
|
|
33
|
+
stack => stack.modalProps.preventRouteChange === true,
|
|
34
|
+
);
|
|
32
35
|
|
|
33
36
|
useEffect(() => {
|
|
34
37
|
// 변경 설명: closeModal이 최신 stack 상태를 기준으로 대상을 찾도록 현재 스냅샷 ref를 동기화한다.
|
|
@@ -130,5 +133,5 @@ export function useModal(): UseModalReturn {
|
|
|
130
133
|
[updateModalStack],
|
|
131
134
|
);
|
|
132
135
|
|
|
133
|
-
return { modalStacks, newModal, updateModal, closeModal };
|
|
136
|
+
return { modalStacks, hasBlockingModal, newModal, updateModal, closeModal };
|
|
134
137
|
}
|
package/src/modal/types/hooks.ts
CHANGED
|
@@ -26,6 +26,7 @@ export type CloseFlagState = {
|
|
|
26
26
|
/**
|
|
27
27
|
* useModal 훅 반환값.
|
|
28
28
|
* @property {ModalState[]} modalStacks 현재 모달 스택 목록
|
|
29
|
+
* @property {boolean} hasBlockingModal route 변경 확인이 필요한 모달 존재 여부
|
|
29
30
|
* @property {<FormContext extends FieldValues>(newStack: ModalState<FormContext>) => void} newModal 신규 모달 추가 함수
|
|
30
31
|
* @property {<FormContext extends FieldValues>(nextStack: ModalStatePatch<FormContext>) => void} updateModal 스택 상태 갱신 함수
|
|
31
32
|
* @property {(request: ModalCloseRequest) => void} closeModal close 제어 함수
|
|
@@ -35,6 +36,10 @@ export type UseModalReturn = {
|
|
|
35
36
|
* 현재 모달 스택 목록
|
|
36
37
|
*/
|
|
37
38
|
modalStacks: ModalState[];
|
|
39
|
+
/**
|
|
40
|
+
* route 변경 확인이 필요한 모달 존재 여부
|
|
41
|
+
*/
|
|
42
|
+
hasBlockingModal: boolean;
|
|
38
43
|
/**
|
|
39
44
|
* 신규 모달 추가 함수
|
|
40
45
|
*/
|
package/src/modal/types/state.ts
CHANGED
|
@@ -17,6 +17,8 @@ export type ModalShowState = "init" | boolean;
|
|
|
17
17
|
* @property {ReactNode} [footer] footer 콘텐츠
|
|
18
18
|
* @property {number | string} [width] 커스텀 width
|
|
19
19
|
* @property {StyleSpacingType} [padding] 커스텀 padding
|
|
20
|
+
* @property {boolean} [closeOnOutsideClick] overlay 클릭 close 허용 여부
|
|
21
|
+
* @property {boolean} [preventRouteChange] route 변경 전 확인 필요 여부
|
|
20
22
|
* @property {UseFormProps<FormContext>} [formContextOptions] form context 옵션
|
|
21
23
|
*/
|
|
22
24
|
export type ModalSections<
|
|
@@ -48,6 +50,14 @@ export type ModalSections<
|
|
|
48
50
|
* - [상, 우, 하, 좌]
|
|
49
51
|
*/
|
|
50
52
|
padding?: StyleSpacingType;
|
|
53
|
+
/**
|
|
54
|
+
* overlay 클릭 close 허용 여부
|
|
55
|
+
*/
|
|
56
|
+
closeOnOutsideClick?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* route 변경 전 확인 필요 여부
|
|
59
|
+
*/
|
|
60
|
+
preventRouteChange?: boolean;
|
|
51
61
|
/**
|
|
52
62
|
* form context 옵션
|
|
53
63
|
* - 옵션이 활성화되면, Form.Provider 사용으로 간주하여 활성화시킴
|
|
@@ -69,6 +79,8 @@ export type ModalSections<
|
|
|
69
79
|
* @property {ModalShowState} show 노출 상태
|
|
70
80
|
* @property {number} [showDelay] close 후 제거 지연(ms)
|
|
71
81
|
* @property {ModalFooterButton[]} [footerButtons] footer 버튼 스펙
|
|
82
|
+
* @property {boolean} [closeOnOutsideClick] overlay 클릭 close 허용 여부 (@see ModalSections)
|
|
83
|
+
* @property {boolean} [preventRouteChange] route 변경 전 확인 필요 여부 (@see ModalSections)
|
|
72
84
|
* @property {UseFormProps<FormContext>} [formContextOptions] form context 옵션
|
|
73
85
|
*/
|
|
74
86
|
export type ModalProps<
|
|
@@ -56,6 +56,8 @@ export type ModalTemplateButtonSpec = {
|
|
|
56
56
|
* @property {number} [showDelay] close 후 제거 지연(ms)
|
|
57
57
|
* @property {number | string} [width] 커스텀 width
|
|
58
58
|
* @property {StyleSpacingType} [padding] 커스텀 padding
|
|
59
|
+
* @property {boolean} [closeOnOutsideClick] overlay 클릭 close 허용 여부
|
|
60
|
+
* @property {boolean} [preventRouteChange] route 변경 전 확인 필요 여부
|
|
59
61
|
*/
|
|
60
62
|
type ModalTemplateBase<
|
|
61
63
|
FormContext extends FieldValues = Record<string, unknown>,
|
|
@@ -82,6 +84,14 @@ type ModalTemplateBase<
|
|
|
82
84
|
* - [상, 우, 하, 좌]
|
|
83
85
|
*/
|
|
84
86
|
padding?: StyleSpacingType;
|
|
87
|
+
/**
|
|
88
|
+
* overlay 클릭 close 허용 여부
|
|
89
|
+
*/
|
|
90
|
+
closeOnOutsideClick?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* route 변경 전 확인 필요 여부
|
|
93
|
+
*/
|
|
94
|
+
preventRouteChange?: boolean;
|
|
85
95
|
/**
|
|
86
96
|
* modal 내부 Form.Provider 옵션
|
|
87
97
|
* - 타입 안정성은 Modal.Alert/Dialog 생성 시점 제네릭으로 보장한다.
|
|
@@ -99,6 +109,8 @@ type ModalTemplateBase<
|
|
|
99
109
|
* @property {ReactNode} [footer] 커스텀 footer
|
|
100
110
|
* @property {number | string} [width] 커스텀 width
|
|
101
111
|
* @property {StyleSpacingType} [padding] 커스텀 padding
|
|
112
|
+
* @property {boolean} [closeOnOutsideClick] overlay 클릭 close 허용 여부
|
|
113
|
+
* @property {boolean} [preventRouteChange] route 변경 전 확인 필요 여부
|
|
102
114
|
* @property {UseFormProps<FormContext>} [formContextOptions] modal 내부 Form.Provider 옵션
|
|
103
115
|
*/
|
|
104
116
|
export type AlertTemplateOptions<
|
|
@@ -145,6 +157,8 @@ export type AlertTemplateOptions<
|
|
|
145
157
|
* @property {ReactNode} [footer] 커스텀 footer
|
|
146
158
|
* @property {number | string} [width] 커스텀 width
|
|
147
159
|
* @property {StyleSpacingType} [padding] 커스텀 padding
|
|
160
|
+
* @property {boolean} [closeOnOutsideClick] overlay 클릭 close 허용 여부
|
|
161
|
+
* @property {boolean} [preventRouteChange] route 변경 전 확인 필요 여부
|
|
148
162
|
* @property {UseFormProps<FormContext>} [formContextOptions] modal 내부 Form.Provider 옵션
|
|
149
163
|
*/
|
|
150
164
|
export type DialogTemplateOptions<
|