@uniai-fe/uds-templates 0.3.14 → 0.4.0

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 CHANGED
@@ -29,6 +29,59 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
29
29
  - **templates**는 레이아웃/플로우/상태 표현까지 담당하고,
30
30
  - API 호출, 인증 토큰 관리, 라우팅, i18n 등 비즈니스 로직은 서비스 앱에서 구현합니다.
31
31
 
32
+ ## 확인 완료 도구 목록
33
+
34
+ - `/modal`
35
+ - `Modal.Provider`
36
+ - `Modal.StackProvider`
37
+ - `Modal.RouteReset`
38
+ - `Modal.useModal`
39
+ - `Modal.Alert`
40
+ - `Modal.Dialog`
41
+ - `createAlertModal`
42
+ - `createDialogModal`
43
+ - `modalStackAtom`
44
+ - `ModalState`
45
+ - `ModalStatePatch`
46
+ - `ModalProps`
47
+ - `ModalFooterButton`
48
+ - `AlertTemplateOptions`
49
+ - `DialogTemplateOptions`
50
+ - `UseModalReturn`
51
+ - `/weather`
52
+ - `WeatherComponents.PageHeader`
53
+ - `WeatherPageHeaderContainer`
54
+ - `WeatherMockProvider`
55
+ - `weatherCoordinate`
56
+ - `useWeatherKorea`
57
+ - `useOpenWeatherMap`
58
+ - `/cctv`
59
+ - `CCTV.Provider`
60
+ - `CCTV.CamList.Container`
61
+ - `CCTV.Video.Container`
62
+ - `CCTV.Video.Contents`
63
+ - `CCTV.Video.Overlay.Container`
64
+ - `CCTV.Pagination.Container`
65
+ - `CCTV.Pagination.List.Container`
66
+ - `CCTV.Pagination.Button.Prev`
67
+ - `CCTV.Pagination.Button.Next`
68
+ - `CCTV.Viewer.Desktop.Container`
69
+ - `useCctvCompanyData`
70
+ - `useCctvContext`
71
+ - `useCctvRtcStream`
72
+ - `getServerCompanyList`
73
+ - `getServerCctvToken`
74
+ - `postCctvRtcToken`
75
+ - `/page-frame`
76
+ - `Frame`
77
+ - `Frame.Mobile`
78
+ - `MobileFrame`
79
+ - `Frame.Desktop`
80
+ - `DesktopFrame`
81
+ - `MobileFrameProps`
82
+ - `PageFrameDesktopNavProps`
83
+ - `SitemapDataType`
84
+
32
85
  ## 현재 제공 템플릿/모듈
33
86
 
34
87
  - `/auth/**`
@@ -42,6 +95,12 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
42
95
  - `/modal/**`
43
96
  - ui-legacy 스택 기반 모달 Provider/Root/Container + 템플릿(`Modal.Alert`, `Modal.Dialog`)
44
97
  - Storybook(`apps/design-storybook/src/stories/templates/modal`)에서 Alert/Confirm 케이스를 검증한다.
98
+ - `/weather/**`
99
+ - page-frame header utility에 결합되는 weather header 템플릿과 weather data hook/mock 도구를 제공한다.
100
+ - `/cctv/**`
101
+ - finder/viewer/video/pagination 조합과 rtc/company-list API helper를 제공한다.
102
+ - `/page-frame/**`
103
+ - mobile/desktop private route frame, nav/header/popup 조합을 제공한다.
45
104
 
46
105
  각 템플릿의 상세한 범위와 의사결정은 `CONTEXT-*.md` 문서에서 관리합니다.
47
106
 
@@ -142,7 +201,7 @@ ui-legacy에서 사용하던 모달 스택/옵션을 templates 레이어로 옮
142
201
 
143
202
  1. **Provider 1회 장착** — 서비스 레이아웃에서 `<Modal.Provider />`를 렌더합니다.
144
203
  2. **Route Reset 장착** — `<Modal.RouteReset />`을 Provider와 같은 레벨에서 렌더해 경로 변경 시 스택을 초기화합니다.
145
- 3. **훅 사용** — `const { newModal, closeModal } = Modal.useModal();`
204
+ 3. **훅 사용** — `const { newModal, closeModal, hasBlockingModal } = Modal.useModal();`
146
205
  4. **템플릿 팩토리** — `Modal.Alert`, `Modal.Dialog`이 `ModalState` 객체를 반환하므로 `newModal(...)`에 그대로 전달합니다.
147
206
 
148
207
  ```tsx
@@ -211,6 +270,8 @@ export function ExampleActions() {
211
270
  ```
212
271
 
213
272
  - footer 버튼은 ui-legacy와 동일하게 `stackKey`, `role`, `defaultOptions/linkOptions` 구조로 관리하며, Alert(Text)/Dialog(Solid) 규격은 templates 내부에서 보장합니다.
273
+ - `closeOnOutsideClick?: boolean`으로 overlay 클릭 닫힘을 제어할 수 있습니다. 기본값은 `true`입니다.
274
+ - `preventRouteChange?: boolean`은 현재 열린 modal 중 route 변경 확인이 필요한 modal이 있는지 집계할 때 사용합니다. 서비스 앱은 `hasBlockingModal`을 읽어 공통 confirm UX를 연결할 수 있습니다.
214
275
  - Route Reset을 누락하면 이전 라우트의 모달 스택이 그대로 남으므로, layout 수준에서 Provider와 함께 반드시 렌더합니다.
215
276
  - 세부 가드레일·확장 기록은 `CONTEXT-MODAL.md`를 참고하고, 새 템플릿을 추가할 때 해당 문서를 선행 업데이트합니다.
216
277
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-templates",
3
- "version": "0.3.14",
3
+ "version": "0.4.0",
4
4
  "description": "UNIAI Design System; UI Templates Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -4,6 +4,9 @@ import CCTVProvider from "./Provider";
4
4
  import { CCTVVideo } from "./video";
5
5
  import { CCTVViewer } from "./viewer";
6
6
 
7
+ /**
8
+ * CCTV Components; CCTV component namespace 집계
9
+ */
7
10
  export const CCTV = {
8
11
  Provider: CCTVProvider,
9
12
  Video: CCTVVideo,
@@ -1,5 +1,8 @@
1
1
  import "./index.scss";
2
2
 
3
+ /**
4
+ * CCTV; viewer, cam-list, pagination, api/hook/jotai 도구를 함께 제공하는 엔트리
5
+ */
3
6
  export * from "./apis";
4
7
  export * from "./data";
5
8
  export * from "./components";
@@ -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
  }
@@ -11,6 +11,9 @@ import {
11
11
  createDialogModal,
12
12
  } from "./components";
13
13
 
14
+ /**
15
+ * Modal; namespace와 direct export를 함께 제공하는 modal 엔트리
16
+ */
14
17
  export const Modal = ModalNamespace;
15
18
 
16
19
  export {
@@ -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
  */
@@ -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<
@@ -3,6 +3,9 @@ import Nav from "./nav";
3
3
  import Header from "./header";
4
4
  import Popup from "./popup";
5
5
 
6
+ /**
7
+ * Page Frame Desktop Components; desktop frame namespace 집계
8
+ */
6
9
  const Frame = {
7
10
  Page: {
8
11
  ...Page,
@@ -2,6 +2,9 @@ import "./index.scss";
2
2
 
3
3
  import Frame from "./components";
4
4
 
5
+ /**
6
+ * Page Frame Desktop; desktop frame 엔트리
7
+ */
5
8
  export type * from "./types";
6
9
  export * from "./data/setting";
7
10
 
@@ -1,6 +1,9 @@
1
1
  import { DesktopFrame } from "./desktop";
2
2
  import { MobileFrame } from "./mobile";
3
3
 
4
+ /**
5
+ * Frame; desktop/mobile page-frame namespace 집계
6
+ */
4
7
  export * from "./mobile";
5
8
  export * from "./types";
6
9
  export * from "./desktop";
@@ -2,6 +2,9 @@ import Page from "./page";
2
2
  import Header from "./header";
3
3
  import Navigation from "./navigation";
4
4
 
5
+ /**
6
+ * Page Frame Mobile Components; mobile frame namespace 집계
7
+ */
5
8
  const Frame = {
6
9
  Page,
7
10
  Header,
@@ -2,6 +2,9 @@ import "./index.scss";
2
2
 
3
3
  import { MobileFrame } from "./components/page/Frame";
4
4
 
5
+ /**
6
+ * Page Frame Mobile; mobile frame 엔트리
7
+ */
5
8
  export type { MobileFrameProps } from "./types";
6
9
  export * from "./components";
7
10
  export { MobileFrame };
@@ -1,5 +1,8 @@
1
1
  import WeatherPageHeaderContainer from "./page-header/Container";
2
2
 
3
+ /**
4
+ * Weather Components; weather component namespace 집계
5
+ */
3
6
  const Weather = {
4
7
  PageHeader: WeatherPageHeaderContainer,
5
8
  };
@@ -1,6 +1,9 @@
1
1
  // Weather UI 컴포넌트 전용 스타일을 로드한다.
2
2
  import "./styles/weather.scss";
3
3
 
4
+ /**
5
+ * Weather; page-header 템플릿과 api/hook/jotai/mock 도구를 함께 제공하는 엔트리
6
+ */
4
7
  export * from "./utils";
5
8
  export * from "./apis";
6
9
  export * from "./jotai";