@yeongseoksong/framework 1.5.0 → 1.6.1
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 +48 -13
- package/dist/store/index.cjs +11 -2
- package/dist/store/index.d.mts +31 -1
- package/dist/store/index.d.ts +31 -1
- package/dist/store/index.mjs +10 -2
- package/dist/ui/index.cjs +249 -208
- package/dist/ui/index.d.mts +44 -4
- package/dist/ui/index.d.ts +44 -4
- package/dist/ui/index.mjs +206 -168
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,9 @@ pnpm add @mantine/core @mantine/hooks @mantine/carousel @mantine/notifications @
|
|
|
16
16
|
|
|
17
17
|
## 설정
|
|
18
18
|
|
|
19
|
-
### 1.
|
|
19
|
+
### 1. SdProvider
|
|
20
|
+
|
|
21
|
+
`SdProvider` 하나가 **프로바이더/컨텍스트 조립**을 묶습니다 — `MantineProvider`(테마) + `SdToastProvider`(토스트 렌더 지점) + `NavProvider`(navItems Context). 이걸 최상단에 두고, 헤더/푸터 크롬은 `MainLayout`(또는 페이지별 `PageLayout`)으로 그 **안쪽**에 배치하세요.
|
|
20
22
|
|
|
21
23
|
```tsx
|
|
22
24
|
// app/layout.tsx
|
|
@@ -24,24 +26,41 @@ import '@mantine/core/styles.css'
|
|
|
24
26
|
import '@mantine/carousel/styles.css'
|
|
25
27
|
import '@mantine/notifications/styles.css'
|
|
26
28
|
import '@mantine/dates/styles.css'
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
+
import { ColorSchemeScript, mantineHtmlProps } from '@mantine/core'
|
|
30
|
+
import { SdProvider, MainLayout } from '@yeongseoksong/framework/ui'
|
|
31
|
+
import { navItems, companyInfo } from './data'
|
|
29
32
|
|
|
30
33
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
31
34
|
return (
|
|
32
|
-
<html lang="ko">
|
|
35
|
+
<html lang="ko" {...mantineHtmlProps}>
|
|
36
|
+
<head>
|
|
37
|
+
<ColorSchemeScript defaultColorScheme="light" />
|
|
38
|
+
</head>
|
|
33
39
|
<body>
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
<SdProvider navItems={navItems}>
|
|
41
|
+
<MainLayout navItems={navItems} companyInfo={companyInfo}>
|
|
42
|
+
{children}
|
|
43
|
+
</MainLayout>
|
|
44
|
+
</SdProvider>
|
|
38
45
|
</body>
|
|
39
46
|
</html>
|
|
40
47
|
)
|
|
41
48
|
}
|
|
42
49
|
```
|
|
43
50
|
|
|
44
|
-
|
|
51
|
+
**SdProvider props**
|
|
52
|
+
|
|
53
|
+
| prop | 기본값 | 설명 |
|
|
54
|
+
| -------------------- | ------------------ | -------------------------------------------------------------------- |
|
|
55
|
+
| `navItems` | (필수) | `NavProvider` Context에 얹을 네비게이션 트리 |
|
|
56
|
+
| `theme` | 프레임워크 기본 | 색 오버라이드는 `mergeThemeOverrides(theme, …)` 결과를 넘긴다(2번 참고) |
|
|
57
|
+
| `defaultColorScheme` | `light` | Mantine 색 구성 |
|
|
58
|
+
|
|
59
|
+
헤더/푸터를 안 붙이는 화면(로그인 등)은 `MainLayout` 없이 `SdProvider` 안에 바로 내용을 둡니다. 헤더 변형(`mega`/`simple`/`panel`)·`loginFlag`·`companyInfo`는 이제 `MainLayout` prop입니다(아래 MainLayout 항목).
|
|
60
|
+
|
|
61
|
+
`ColorSchemeScript`와 Mantine CSS import는 `<head>`/서버 layout 몫이라 `SdProvider`에 포함되지 않습니다. 회사명/로고는 `SdProvider`가 아니라 env var로 주입합니다(2번 참고).
|
|
62
|
+
|
|
63
|
+
내부의 `SdToastProvider`는 토스트가 실제로 그려지는 자리입니다 — `SdProvider`를 쓰면 자동으로 포함됩니다(아래 SdToast 항목 참고). `SdProvider`는 `navItems`를 `NavProvider`(React Context)로도 감싸므로, 그 아래의 `SdBreadcrumb`는 `navItems` prop 없이도 동작합니다.
|
|
45
64
|
|
|
46
65
|
### 2. 환경변수 설정
|
|
47
66
|
|
|
@@ -99,10 +118,9 @@ export const appTheme = mergeThemeOverrides(theme, {
|
|
|
99
118
|
|
|
100
119
|
```tsx
|
|
101
120
|
// app/layout.tsx — Server Component 그대로 둡니다
|
|
102
|
-
import { MantineProvider } from '@mantine/core'
|
|
103
121
|
import { appTheme } from './theme'
|
|
104
122
|
|
|
105
|
-
<
|
|
123
|
+
<SdProvider theme={appTheme} navItems={navItems}>
|
|
106
124
|
```
|
|
107
125
|
|
|
108
126
|
오버라이드하지 않은 키(타이포·spacing·shadows·컴포넌트 기본값)는 프레임워크 값이 그대로 유지됩니다.
|
|
@@ -125,7 +143,7 @@ import { appTheme } from './theme'
|
|
|
125
143
|
| 경로 | 내용 |
|
|
126
144
|
| -------------------------------- | --------------------------------------------- |
|
|
127
145
|
| `@yeongseoksong/framework/ui` | UI 컴포넌트 전체 + `theme` (`"use client"`) |
|
|
128
|
-
| `@yeongseoksong/framework/store` | Zustand 스토어 — `useAuthStore` · `useUiStore` · `useSdForm` (`"use client"`) |
|
|
146
|
+
| `@yeongseoksong/framework/store` | Zustand 스토어 — `useAuthStore` · `useUiStore` · `useNavStore` · `useSdForm` (`"use client"`) |
|
|
129
147
|
| `@yeongseoksong/framework/util` | `t()`, 한글 조사(`josa` · `withJosa` · `fixJosa`), `runFinalizers`, `filterAndSort`, `COMPANY_NAME`, `LOGO_SRC`, `LOGO_ALT` |
|
|
130
148
|
| `@yeongseoksong/framework/types` | 공유 인터페이스 |
|
|
131
149
|
|
|
@@ -567,7 +585,7 @@ export default function LoginPage() {
|
|
|
567
585
|
`Split`은 `brandTitle`/`brandDescription`으로 좌측 패널 문구를 받고(기본값 `%c`), 브랜드 면은 `PageLayout.Brand` 히어로와 같은 배경(`ui/surface.ts`)을 씁니다. 좌측 패널은 `md` 미만에서 숨겨져 폼만 남습니다.
|
|
568
586
|
전체 화면이 기본(`mih="100svh"`)이므로, 좁은 영역에 끼워 넣을 때만 `mih`를 줄입니다.
|
|
569
587
|
|
|
570
|
-
### 상태 관리 — useAuthStore / useUiStore
|
|
588
|
+
### 상태 관리 — useAuthStore / useUiStore / useNavStore
|
|
571
589
|
|
|
572
590
|
전역 클라이언트 상태는 **Zustand** 스토어로 **`@yeongseoksong/framework/store`** 경로에서 제공됩니다. Provider가 없으므로 어디서든 훅으로 바로 읽고 씁니다.
|
|
573
591
|
|
|
@@ -612,6 +630,23 @@ const setGlobalLoading = useUiStore((s) => s.setGlobalLoading)
|
|
|
612
630
|
|
|
613
631
|
`SdHeader`의 모바일 드로어는 일부러 이 스토어를 쓰지 않습니다 — 한 페이지에 헤더가 둘 이상 있으면 드로어가 함께 열리므로 인스턴스 로컬 상태로 남겨 두었습니다.
|
|
614
632
|
|
|
633
|
+
`useNavStore`는 네비게이션 상태(`navItems` + `setNavItems`)를 담습니다 — 렌더 트리 **밖**에서 navItems가 필요할 때(라우팅 로직 등) 씁니다.
|
|
634
|
+
|
|
635
|
+
```tsx
|
|
636
|
+
import { useNavStore } from '@yeongseoksong/framework/store'
|
|
637
|
+
useNavStore.getState().setNavItems(navItems)
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
트리 **안** 컴포넌트(`SdBreadcrumb`)는 이 스토어가 아니라 `@yeongseoksong/framework/ui`의 `NavProvider`(React Context) + `useNav`로 navItems를 읽습니다 — `ui` 번들이 스토어를 직접 import하면 `dist/ui`에 인라인돼 인스턴스가 갈라지기 때문입니다. `SdProvider`가 `NavProvider`를 자동으로 감싸므로, 보통은 소비자가 직접 다룰 일이 없습니다.
|
|
641
|
+
|
|
642
|
+
```tsx
|
|
643
|
+
import { NavProvider, useNav, SdBreadcrumb } from '@yeongseoksong/framework/ui'
|
|
644
|
+
|
|
645
|
+
<NavProvider navItems={navItems}>
|
|
646
|
+
<SdBreadcrumb /> {/* navItems prop 없이 useNav()로 읽음 */}
|
|
647
|
+
</NavProvider>
|
|
648
|
+
```
|
|
649
|
+
|
|
615
650
|
### 폼 — useSdForm
|
|
616
651
|
|
|
617
652
|
모든 폼이 같은 방식으로 값·검증·제출을 다루도록 훅 하나로 통일했습니다.
|
package/dist/store/index.cjs
CHANGED
|
@@ -75,6 +75,7 @@ __export(store_exports, {
|
|
|
75
75
|
useAuthHydrated: () => useAuthHydrated,
|
|
76
76
|
useAuthStore: () => useAuthStore,
|
|
77
77
|
useFormStore: () => useFormStore,
|
|
78
|
+
useNavStore: () => useNavStore,
|
|
78
79
|
useSdForm: () => useSdForm,
|
|
79
80
|
useUiStore: () => useUiStore
|
|
80
81
|
});
|
|
@@ -125,9 +126,16 @@ var useUiStore = (0, import_zustand2.create)()((set) => ({
|
|
|
125
126
|
toggleSideNav: () => set((state) => ({ sideNavOpened: !state.sideNavOpened }))
|
|
126
127
|
}));
|
|
127
128
|
|
|
129
|
+
// store/nav.store.ts
|
|
130
|
+
var import_zustand3 = require("zustand");
|
|
131
|
+
var useNavStore = (0, import_zustand3.create)()((set) => ({
|
|
132
|
+
navItems: [],
|
|
133
|
+
setNavItems: (navItems) => set({ navItems })
|
|
134
|
+
}));
|
|
135
|
+
|
|
128
136
|
// store/form.state.ts
|
|
129
137
|
var import_react2 = require("react");
|
|
130
|
-
var
|
|
138
|
+
var import_zustand4 = require("zustand");
|
|
131
139
|
|
|
132
140
|
// ui/atom/Toast.tsx
|
|
133
141
|
var import_notifications = require("@mantine/notifications");
|
|
@@ -216,7 +224,7 @@ function patch(state, formId, next) {
|
|
|
216
224
|
if (!current) return state;
|
|
217
225
|
return __spreadProps(__spreadValues({}, state), { forms: __spreadProps(__spreadValues({}, state.forms), { [formId]: __spreadValues(__spreadValues({}, current), next) }) });
|
|
218
226
|
}
|
|
219
|
-
var useFormStore = (0,
|
|
227
|
+
var useFormStore = (0, import_zustand4.create)()((set) => ({
|
|
220
228
|
forms: {},
|
|
221
229
|
ensureForm: (formId, initialValues) => set(
|
|
222
230
|
(state) => state.forms[formId] ? state : __spreadProps(__spreadValues({}, state), { forms: __spreadProps(__spreadValues({}, state.forms), { [formId]: blankEntry(initialValues) }) })
|
|
@@ -379,6 +387,7 @@ var formRules = {
|
|
|
379
387
|
useAuthHydrated,
|
|
380
388
|
useAuthStore,
|
|
381
389
|
useFormStore,
|
|
390
|
+
useNavStore,
|
|
382
391
|
useSdForm,
|
|
383
392
|
useUiStore
|
|
384
393
|
});
|
package/dist/store/index.d.mts
CHANGED
|
@@ -81,6 +81,36 @@ interface UiState {
|
|
|
81
81
|
*/
|
|
82
82
|
declare const useUiStore: zustand.UseBoundStore<zustand.StoreApi<UiState>>;
|
|
83
83
|
|
|
84
|
+
interface CommonInfo {
|
|
85
|
+
id: number;
|
|
86
|
+
order: number;
|
|
87
|
+
isShow: boolean;
|
|
88
|
+
createdAt?: Date;
|
|
89
|
+
updatedAt?: Date;
|
|
90
|
+
}
|
|
91
|
+
interface NavItem extends CommonInfo {
|
|
92
|
+
label: string;
|
|
93
|
+
href?: string;
|
|
94
|
+
highlight?: boolean;
|
|
95
|
+
parentId?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface NavState {
|
|
99
|
+
/** 앱 전역 네비게이션 트리. 소비자가 앱 진입 시 `setNavItems`로 한 번 주입한다. */
|
|
100
|
+
navItems: NavItem[];
|
|
101
|
+
setNavItems: (navItems: NavItem[]) => void;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 네비게이션 상태 스토어 — 렌더 트리 **밖에서** navItems에 접근해야 할 때 쓴다
|
|
105
|
+
* (프로그램적 네비게이션, 라우팅 로직 등). 소비자가 `setNavItems`로 채운다.
|
|
106
|
+
*
|
|
107
|
+
* 트리 **안** 컴포넌트(`SdBreadcrumb`)는 이 스토어가 아니라 `ui/template/NavProvider`의
|
|
108
|
+
* React Context(`useNav`)에서 navItems를 읽는다 — `ui` 번들이 스토어를 import하면
|
|
109
|
+
* `dist/ui`에 인라인돼 `create()`가 두 번 도는 dual-bundle 문제가 생기기 때문이다
|
|
110
|
+
* (store/index.ts 상단 주석 참고). Context는 모듈 상태가 없어 그 문제에서 자유롭다.
|
|
111
|
+
*/
|
|
112
|
+
declare const useNavStore: zustand.UseBoundStore<zustand.StoreApi<NavState>>;
|
|
113
|
+
|
|
84
114
|
/**
|
|
85
115
|
* 어떤 작업이 끝난 뒤 항상 돌려야 하는 뒷정리 — 라우팅, 모달 닫기, 목록 새로고침 등.
|
|
86
116
|
*
|
|
@@ -204,4 +234,4 @@ declare const formRules: {
|
|
|
204
234
|
sameAs: <V extends FormValues>(other: keyof V, message?: string) => FormRule<V>;
|
|
205
235
|
};
|
|
206
236
|
|
|
207
|
-
export { type AuthUser, type FormErrors, type FormRule, type FormValues, type SdFormApi, type SdFormOptions, formRules, useAuthHydrated, useAuthStore, useFormStore, useSdForm, useUiStore };
|
|
237
|
+
export { type AuthUser, type FormErrors, type FormRule, type FormValues, type SdFormApi, type SdFormOptions, formRules, useAuthHydrated, useAuthStore, useFormStore, useNavStore, useSdForm, useUiStore };
|
package/dist/store/index.d.ts
CHANGED
|
@@ -81,6 +81,36 @@ interface UiState {
|
|
|
81
81
|
*/
|
|
82
82
|
declare const useUiStore: zustand.UseBoundStore<zustand.StoreApi<UiState>>;
|
|
83
83
|
|
|
84
|
+
interface CommonInfo {
|
|
85
|
+
id: number;
|
|
86
|
+
order: number;
|
|
87
|
+
isShow: boolean;
|
|
88
|
+
createdAt?: Date;
|
|
89
|
+
updatedAt?: Date;
|
|
90
|
+
}
|
|
91
|
+
interface NavItem extends CommonInfo {
|
|
92
|
+
label: string;
|
|
93
|
+
href?: string;
|
|
94
|
+
highlight?: boolean;
|
|
95
|
+
parentId?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface NavState {
|
|
99
|
+
/** 앱 전역 네비게이션 트리. 소비자가 앱 진입 시 `setNavItems`로 한 번 주입한다. */
|
|
100
|
+
navItems: NavItem[];
|
|
101
|
+
setNavItems: (navItems: NavItem[]) => void;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 네비게이션 상태 스토어 — 렌더 트리 **밖에서** navItems에 접근해야 할 때 쓴다
|
|
105
|
+
* (프로그램적 네비게이션, 라우팅 로직 등). 소비자가 `setNavItems`로 채운다.
|
|
106
|
+
*
|
|
107
|
+
* 트리 **안** 컴포넌트(`SdBreadcrumb`)는 이 스토어가 아니라 `ui/template/NavProvider`의
|
|
108
|
+
* React Context(`useNav`)에서 navItems를 읽는다 — `ui` 번들이 스토어를 import하면
|
|
109
|
+
* `dist/ui`에 인라인돼 `create()`가 두 번 도는 dual-bundle 문제가 생기기 때문이다
|
|
110
|
+
* (store/index.ts 상단 주석 참고). Context는 모듈 상태가 없어 그 문제에서 자유롭다.
|
|
111
|
+
*/
|
|
112
|
+
declare const useNavStore: zustand.UseBoundStore<zustand.StoreApi<NavState>>;
|
|
113
|
+
|
|
84
114
|
/**
|
|
85
115
|
* 어떤 작업이 끝난 뒤 항상 돌려야 하는 뒷정리 — 라우팅, 모달 닫기, 목록 새로고침 등.
|
|
86
116
|
*
|
|
@@ -204,4 +234,4 @@ declare const formRules: {
|
|
|
204
234
|
sameAs: <V extends FormValues>(other: keyof V, message?: string) => FormRule<V>;
|
|
205
235
|
};
|
|
206
236
|
|
|
207
|
-
export { type AuthUser, type FormErrors, type FormRule, type FormValues, type SdFormApi, type SdFormOptions, formRules, useAuthHydrated, useAuthStore, useFormStore, useSdForm, useUiStore };
|
|
237
|
+
export { type AuthUser, type FormErrors, type FormRule, type FormValues, type SdFormApi, type SdFormOptions, formRules, useAuthHydrated, useAuthStore, useFormStore, useNavStore, useSdForm, useUiStore };
|
package/dist/store/index.mjs
CHANGED
|
@@ -97,9 +97,16 @@ var useUiStore = create2()((set) => ({
|
|
|
97
97
|
toggleSideNav: () => set((state) => ({ sideNavOpened: !state.sideNavOpened }))
|
|
98
98
|
}));
|
|
99
99
|
|
|
100
|
+
// store/nav.store.ts
|
|
101
|
+
import { create as create3 } from "zustand";
|
|
102
|
+
var useNavStore = create3()((set) => ({
|
|
103
|
+
navItems: [],
|
|
104
|
+
setNavItems: (navItems) => set({ navItems })
|
|
105
|
+
}));
|
|
106
|
+
|
|
100
107
|
// store/form.state.ts
|
|
101
108
|
import { useCallback, useEffect as useEffect2, useMemo } from "react";
|
|
102
|
-
import { create as
|
|
109
|
+
import { create as create4 } from "zustand";
|
|
103
110
|
|
|
104
111
|
// ui/atom/Toast.tsx
|
|
105
112
|
import { Notifications, notifications } from "@mantine/notifications";
|
|
@@ -188,7 +195,7 @@ function patch(state, formId, next) {
|
|
|
188
195
|
if (!current) return state;
|
|
189
196
|
return __spreadProps(__spreadValues({}, state), { forms: __spreadProps(__spreadValues({}, state.forms), { [formId]: __spreadValues(__spreadValues({}, current), next) }) });
|
|
190
197
|
}
|
|
191
|
-
var useFormStore =
|
|
198
|
+
var useFormStore = create4()((set) => ({
|
|
192
199
|
forms: {},
|
|
193
200
|
ensureForm: (formId, initialValues) => set(
|
|
194
201
|
(state) => state.forms[formId] ? state : __spreadProps(__spreadValues({}, state), { forms: __spreadProps(__spreadValues({}, state.forms), { [formId]: blankEntry(initialValues) }) })
|
|
@@ -350,6 +357,7 @@ export {
|
|
|
350
357
|
useAuthHydrated,
|
|
351
358
|
useAuthStore,
|
|
352
359
|
useFormStore,
|
|
360
|
+
useNavStore,
|
|
353
361
|
useSdForm,
|
|
354
362
|
useUiStore
|
|
355
363
|
};
|