@sonhoseong/mfa-lib 1.3.0 → 1.3.2

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.
@@ -0,0 +1,356 @@
1
+ /* ============================================
2
+ AppSidebar - KOMCA 패턴
3
+ 사이드바 네비게이션 스타일
4
+ ============================================ */
5
+
6
+ .app-sidebar {
7
+ position: fixed;
8
+ top: 0;
9
+ left: 0;
10
+ width: 260px;
11
+ height: 100vh;
12
+ background: #ffffff;
13
+ border-right: 1px solid var(--color-border, #E2E8F0);
14
+ display: flex;
15
+ flex-direction: column;
16
+ z-index: 100;
17
+ transition: width 0.3s ease, transform 0.3s ease;
18
+ }
19
+
20
+ /* Collapsed State */
21
+ .app-sidebar.collapsed {
22
+ width: 72px;
23
+ }
24
+
25
+ /* Toggle Button (outside sidebar) */
26
+ .sidebar-toggle {
27
+ position: fixed;
28
+ top: 16px;
29
+ left: 268px;
30
+ width: 32px;
31
+ height: 32px;
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ background: #ffffff;
36
+ border: 1px solid var(--color-border, #E2E8F0);
37
+ border-radius: 8px;
38
+ cursor: pointer;
39
+ z-index: 101;
40
+ transition: all 0.3s ease;
41
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
42
+ }
43
+
44
+ .sidebar-toggle:hover {
45
+ background: var(--color-bg-secondary, #F8FAFC);
46
+ border-color: var(--color-accent, #0EA5E9);
47
+ color: var(--color-accent, #0EA5E9);
48
+ }
49
+
50
+ .sidebar-toggle.collapsed {
51
+ left: 80px;
52
+ }
53
+
54
+ .sidebar-toggle svg {
55
+ color: var(--color-text-secondary, #64748B);
56
+ transition: color 0.2s ease;
57
+ }
58
+
59
+ .sidebar-toggle:hover svg {
60
+ color: var(--color-accent, #0EA5E9);
61
+ }
62
+
63
+ /* Sidebar Header */
64
+ .sidebar-header {
65
+ padding: 20px;
66
+ border-bottom: 1px solid var(--color-border, #E2E8F0);
67
+ transition: padding 0.3s ease;
68
+ }
69
+
70
+ .app-sidebar.collapsed .sidebar-header {
71
+ padding: 16px 12px;
72
+ }
73
+
74
+ .sidebar-logo {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 12px;
78
+ padding: 8px 12px;
79
+ background: transparent;
80
+ border: none;
81
+ border-radius: 12px;
82
+ cursor: pointer;
83
+ transition: background 0.2s ease, padding 0.3s ease, justify-content 0.3s ease;
84
+ width: 100%;
85
+ }
86
+
87
+ .app-sidebar.collapsed .sidebar-logo {
88
+ padding: 8px;
89
+ justify-content: center;
90
+ }
91
+
92
+ .sidebar-logo:hover {
93
+ background: var(--color-bg-secondary, #F8FAFC);
94
+ }
95
+
96
+ .sidebar-app-name {
97
+ font-size: 18px;
98
+ font-weight: 700;
99
+ color: var(--color-primary, #1E3A5F);
100
+ white-space: nowrap;
101
+ overflow: hidden;
102
+ transition: opacity 0.2s ease, width 0.3s ease;
103
+ }
104
+
105
+ /* Sidebar Navigation */
106
+ .sidebar-nav {
107
+ flex: 1;
108
+ overflow-y: auto;
109
+ padding: 16px 12px;
110
+ transition: padding 0.3s ease;
111
+ }
112
+
113
+ .app-sidebar.collapsed .sidebar-nav {
114
+ padding: 16px 8px;
115
+ }
116
+
117
+ .sidebar-menu {
118
+ list-style: none;
119
+ margin: 0;
120
+ padding: 0;
121
+ }
122
+
123
+ .sidebar-menu-item {
124
+ margin-bottom: 4px;
125
+ }
126
+
127
+ .sidebar-menu-btn {
128
+ display: flex;
129
+ align-items: center;
130
+ gap: 12px;
131
+ width: 100%;
132
+ padding: 12px 16px;
133
+ font-family: inherit;
134
+ font-size: 14px;
135
+ font-weight: 500;
136
+ color: var(--color-text-secondary, #64748B);
137
+ background: transparent;
138
+ border: none;
139
+ border-radius: 10px;
140
+ cursor: pointer;
141
+ transition: all 0.2s ease;
142
+ text-align: left;
143
+ }
144
+
145
+ .app-sidebar.collapsed .sidebar-menu-btn {
146
+ padding: 12px;
147
+ justify-content: center;
148
+ }
149
+
150
+ .sidebar-menu-btn:hover {
151
+ background: var(--color-bg-secondary, #F8FAFC);
152
+ color: var(--color-primary, #1E3A5F);
153
+ }
154
+
155
+ .sidebar-menu-btn.active {
156
+ background: rgba(14, 165, 233, 0.1);
157
+ color: var(--color-accent, #0EA5E9);
158
+ }
159
+
160
+ .sidebar-menu-btn.active .sidebar-menu-icon {
161
+ color: var(--color-accent, #0EA5E9);
162
+ }
163
+
164
+ .sidebar-menu-btn.child {
165
+ padding-left: 48px;
166
+ font-size: 13px;
167
+ }
168
+
169
+ .sidebar-menu-icon {
170
+ display: flex;
171
+ align-items: center;
172
+ justify-content: center;
173
+ width: 20px;
174
+ height: 20px;
175
+ color: var(--color-text-muted, #94A3B8);
176
+ transition: color 0.2s ease;
177
+ }
178
+
179
+ .sidebar-menu-btn:hover .sidebar-menu-icon {
180
+ color: var(--color-primary, #1E3A5F);
181
+ }
182
+
183
+ .sidebar-menu-title {
184
+ flex: 1;
185
+ }
186
+
187
+ .sidebar-menu-arrow {
188
+ color: var(--color-text-muted, #94A3B8);
189
+ transition: transform 0.2s ease;
190
+ }
191
+
192
+ .sidebar-menu-arrow.expanded {
193
+ transform: rotate(90deg);
194
+ }
195
+
196
+ /* Submenu */
197
+ .sidebar-submenu {
198
+ list-style: none;
199
+ margin: 4px 0 0 0;
200
+ padding: 0;
201
+ }
202
+
203
+ /* Sidebar Footer */
204
+ .sidebar-footer {
205
+ padding: 16px;
206
+ border-top: 1px solid var(--color-border, #E2E8F0);
207
+ transition: padding 0.3s ease;
208
+ }
209
+
210
+ .app-sidebar.collapsed .sidebar-footer {
211
+ padding: 12px 8px;
212
+ }
213
+
214
+ .sidebar-user {
215
+ display: flex;
216
+ align-items: center;
217
+ gap: 12px;
218
+ padding: 8px;
219
+ background: var(--color-bg-secondary, #F8FAFC);
220
+ border-radius: 12px;
221
+ transition: justify-content 0.3s ease;
222
+ }
223
+
224
+ .sidebar-user.collapsed {
225
+ justify-content: center;
226
+ padding: 8px;
227
+ }
228
+
229
+ .sidebar-user-avatar {
230
+ width: 40px;
231
+ height: 40px;
232
+ display: flex;
233
+ align-items: center;
234
+ justify-content: center;
235
+ background: linear-gradient(135deg, var(--color-primary, #1E3A5F), var(--color-accent, #0EA5E9));
236
+ color: white;
237
+ font-size: 16px;
238
+ font-weight: 600;
239
+ border-radius: 10px;
240
+ flex-shrink: 0;
241
+ }
242
+
243
+ .sidebar-user-info {
244
+ flex: 1;
245
+ min-width: 0;
246
+ display: flex;
247
+ flex-direction: column;
248
+ }
249
+
250
+ .sidebar-user-name {
251
+ font-size: 14px;
252
+ font-weight: 600;
253
+ color: var(--color-primary, #1E3A5F);
254
+ white-space: nowrap;
255
+ overflow: hidden;
256
+ text-overflow: ellipsis;
257
+ }
258
+
259
+ .sidebar-user-email {
260
+ font-size: 12px;
261
+ color: var(--color-text-muted, #94A3B8);
262
+ white-space: nowrap;
263
+ overflow: hidden;
264
+ text-overflow: ellipsis;
265
+ }
266
+
267
+ .sidebar-logout-btn {
268
+ display: flex;
269
+ align-items: center;
270
+ justify-content: center;
271
+ width: 36px;
272
+ height: 36px;
273
+ background: transparent;
274
+ border: none;
275
+ border-radius: 8px;
276
+ color: var(--color-text-muted, #94A3B8);
277
+ cursor: pointer;
278
+ transition: all 0.2s ease;
279
+ flex-shrink: 0;
280
+ }
281
+
282
+ .sidebar-logout-btn:hover {
283
+ background: #FEE2E2;
284
+ color: #DC2626;
285
+ }
286
+
287
+ .sidebar-login-btn {
288
+ display: flex;
289
+ align-items: center;
290
+ justify-content: center;
291
+ gap: 8px;
292
+ width: 100%;
293
+ padding: 12px;
294
+ font-family: inherit;
295
+ font-size: 14px;
296
+ font-weight: 600;
297
+ color: white;
298
+ background: var(--color-primary, #1E3A5F);
299
+ border: none;
300
+ border-radius: 10px;
301
+ cursor: pointer;
302
+ transition: all 0.2s ease;
303
+ }
304
+
305
+ .sidebar-login-btn:hover {
306
+ background: var(--color-accent, #0EA5E9);
307
+ }
308
+
309
+ .sidebar-login-btn.collapsed {
310
+ width: 48px;
311
+ height: 48px;
312
+ padding: 0;
313
+ border-radius: 12px;
314
+ }
315
+
316
+ /* Layout with Sidebar */
317
+ .has-sidebar {
318
+ margin-left: 260px;
319
+ transition: margin-left 0.3s ease;
320
+ }
321
+
322
+ .has-sidebar.collapsed {
323
+ margin-left: 72px;
324
+ }
325
+
326
+ .has-sidebar .main-content {
327
+ min-height: 100vh;
328
+ }
329
+
330
+ /* Responsive */
331
+ @media (max-width: 768px) {
332
+ .app-sidebar {
333
+ transform: translateX(-100%);
334
+ }
335
+
336
+ .app-sidebar.open {
337
+ transform: translateX(0);
338
+ }
339
+
340
+ .has-sidebar {
341
+ margin-left: 0;
342
+ }
343
+
344
+ .has-sidebar.collapsed {
345
+ margin-left: 0;
346
+ }
347
+
348
+ .sidebar-toggle {
349
+ left: 16px;
350
+ top: 16px;
351
+ }
352
+
353
+ .sidebar-toggle.collapsed {
354
+ left: 16px;
355
+ }
356
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * AppSidebar - KOMCA 패턴
3
+ *
4
+ * 사이드바 네비게이션 컴포넌트
5
+ * Remote 앱 단독 실행 시 사용
6
+ */
7
+ import React from 'react';
8
+ export interface SidebarMenuItem {
9
+ id: string;
10
+ title: string;
11
+ path?: string;
12
+ icon?: React.ReactNode;
13
+ children?: SidebarMenuItem[];
14
+ }
15
+ export interface AppSidebarProps {
16
+ /** 앱 이름 */
17
+ appName?: string;
18
+ /** 로그인 여부 */
19
+ isAuthenticated?: boolean;
20
+ /** 사용자 이름 */
21
+ userName?: string;
22
+ /** 사용자 이메일 */
23
+ userEmail?: string;
24
+ /** 메뉴 아이템 */
25
+ menuItems?: SidebarMenuItem[];
26
+ /** 로그아웃 핸들러 */
27
+ onLogout?: () => void;
28
+ /** 네비게이션 핸들러 */
29
+ onNavigate?: (path: string) => void;
30
+ /** 현재 경로 */
31
+ currentPath?: string;
32
+ /** 커스텀 로고 */
33
+ logo?: React.ReactNode;
34
+ /** 접힌 상태 (외부 제어) */
35
+ collapsed?: boolean;
36
+ /** 접힘 상태 변경 콜백 */
37
+ onCollapsedChange?: (collapsed: boolean) => void;
38
+ }
39
+ export declare function AppSidebar({ appName, isAuthenticated, userName, userEmail, menuItems, onLogout, onNavigate, currentPath, logo, collapsed: controlledCollapsed, onCollapsedChange, }: AppSidebarProps): import("react/jsx-runtime").JSX.Element;
40
+ export default AppSidebar;
41
+ //# sourceMappingURL=AppSidebar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppSidebar.d.ts","sourceRoot":"","sources":["../../../src/components/navigation/AppSidebar.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAgC,MAAM,OAAO,CAAC;AAErD,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC5B,WAAW;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa;IACb,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa;IACb,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;IAC9B,eAAe;IACf,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,gBAAgB;IAChB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,YAAY;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa;IACb,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,oBAAoB;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kBAAkB;IAClB,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;CACpD;AAED,wBAAgB,UAAU,CAAC,EACvB,OAAe,EACf,eAAuB,EACvB,QAAQ,EACR,SAAS,EACT,SAAc,EACd,QAAQ,EACR,UAAU,EACV,WAAiB,EACjB,IAAI,EACJ,SAAS,EAAE,mBAAmB,EAC9B,iBAAiB,GACpB,EAAE,eAAe,2CA4KjB;AAED,eAAe,UAAU,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * AppSidebar - KOMCA 패턴
4
+ *
5
+ * 사이드바 네비게이션 컴포넌트
6
+ * Remote 앱 단독 실행 시 사용
7
+ */
8
+ import { useState, useCallback } from 'react';
9
+ export function AppSidebar({ appName = 'MFA', isAuthenticated = false, userName, userEmail, menuItems = [], onLogout, onNavigate, currentPath = '/', logo, collapsed: controlledCollapsed, onCollapsedChange, }) {
10
+ const [expandedMenus, setExpandedMenus] = useState(new Set());
11
+ const [internalCollapsed, setInternalCollapsed] = useState(false);
12
+ // 외부 제어 또는 내부 상태 사용
13
+ const collapsed = controlledCollapsed ?? internalCollapsed;
14
+ const toggleCollapsed = useCallback(() => {
15
+ const newValue = !collapsed;
16
+ setInternalCollapsed(newValue);
17
+ onCollapsedChange?.(newValue);
18
+ }, [collapsed, onCollapsedChange]);
19
+ const toggleMenu = useCallback((menuId) => {
20
+ setExpandedMenus(prev => {
21
+ const next = new Set(prev);
22
+ if (next.has(menuId)) {
23
+ next.delete(menuId);
24
+ }
25
+ else {
26
+ next.add(menuId);
27
+ }
28
+ return next;
29
+ });
30
+ }, []);
31
+ const handleNavigate = useCallback((path) => {
32
+ onNavigate?.(path);
33
+ }, [onNavigate]);
34
+ const renderMenuItem = (item, depth = 0) => {
35
+ const hasChildren = item.children && item.children.length > 0;
36
+ const isExpanded = expandedMenus.has(item.id);
37
+ const isActive = item.path === currentPath;
38
+ return (_jsxs("li", { className: "sidebar-menu-item", children: [_jsxs("button", { className: `sidebar-menu-btn ${isActive ? 'active' : ''} ${depth > 0 ? 'child' : ''}`, onClick: () => {
39
+ if (hasChildren) {
40
+ toggleMenu(item.id);
41
+ }
42
+ else if (item.path) {
43
+ handleNavigate(item.path);
44
+ }
45
+ }, title: collapsed ? item.title : undefined, children: [item.icon && _jsx("span", { className: "sidebar-menu-icon", children: item.icon }), !collapsed && _jsx("span", { className: "sidebar-menu-title", children: item.title }), !collapsed && hasChildren && (_jsx("svg", { className: `sidebar-menu-arrow ${isExpanded ? 'expanded' : ''}`, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("path", { d: "m9 18 6-6-6-6" }) }))] }), !collapsed && hasChildren && isExpanded && (_jsx("ul", { className: "sidebar-submenu", children: item.children.map(child => renderMenuItem(child, depth + 1)) }))] }, item.id));
46
+ };
47
+ return (_jsxs(_Fragment, { children: [_jsx("button", { className: `sidebar-toggle ${collapsed ? 'collapsed' : ''}`, onClick: toggleCollapsed, title: collapsed ? '메뉴 열기' : '메뉴 닫기', children: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: collapsed ? (_jsx("path", { d: "M3 12h18M3 6h18M3 18h18" })) : (_jsx("path", { d: "M18 6L6 18M6 6l12 12" })) }) }), _jsxs("aside", { className: `app-sidebar ${collapsed ? 'collapsed' : ''}`, children: [_jsx("div", { className: "sidebar-header", children: _jsxs("button", { className: "sidebar-logo", onClick: () => handleNavigate('/'), children: [logo || (_jsxs("svg", { viewBox: "0 0 48 48", fill: "none", width: "32", height: "32", children: [_jsx("rect", { x: "20", y: "2", width: "8", height: "16", rx: "4", fill: "#0EA5E9" }), _jsx("rect", { x: "6", y: "16", width: "36", height: "6", rx: "3", fill: "#0EA5E9" }), _jsx("ellipse", { cx: "24", cy: "36", rx: "18", ry: "12", fill: "#0EA5E9" }), _jsx("ellipse", { cx: "17", cy: "36", rx: "4", ry: "6", fill: "#FFFFFF" }), _jsx("ellipse", { cx: "31", cy: "36", rx: "4", ry: "6", fill: "#FFFFFF" })] })), !collapsed && _jsx("span", { className: "sidebar-app-name", children: appName })] }) }), _jsx("nav", { className: "sidebar-nav", children: menuItems.length > 0 ? (_jsx("ul", { className: "sidebar-menu", children: menuItems.map(item => renderMenuItem(item)) })) : (_jsx("ul", { className: "sidebar-menu", children: _jsx("li", { className: "sidebar-menu-item", children: _jsxs("button", { className: `sidebar-menu-btn ${currentPath === '/' ? 'active' : ''}`, onClick: () => handleNavigate('/'), title: collapsed ? '홈' : undefined, children: [_jsx("span", { className: "sidebar-menu-icon", children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("path", { d: "m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }), _jsx("polyline", { points: "9 22 9 12 15 12 15 22" })] }) }), !collapsed && _jsx("span", { className: "sidebar-menu-title", children: "\uD648" })] }) }) })) }), _jsx("div", { className: "sidebar-footer", children: isAuthenticated ? (_jsxs("div", { className: `sidebar-user ${collapsed ? 'collapsed' : ''}`, children: [_jsx("div", { className: "sidebar-user-avatar", title: collapsed ? userName : undefined, children: userName?.charAt(0).toUpperCase() || 'U' }), !collapsed && (_jsxs("div", { className: "sidebar-user-info", children: [_jsx("span", { className: "sidebar-user-name", children: userName || '사용자' }), userEmail && _jsx("span", { className: "sidebar-user-email", children: userEmail })] })), _jsx("button", { className: "sidebar-logout-btn", onClick: onLogout, title: "\uB85C\uADF8\uC544\uC6C3", children: _jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }), _jsx("polyline", { points: "16 17 21 12 16 7" }), _jsx("line", { x1: "21", y1: "12", x2: "9", y2: "12" })] }) })] })) : (_jsxs("button", { className: `sidebar-login-btn ${collapsed ? 'collapsed' : ''}`, onClick: () => handleNavigate('/login'), title: collapsed ? '로그인' : undefined, children: [_jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("path", { d: "M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" }), _jsx("polyline", { points: "10 17 15 12 10 7" }), _jsx("line", { x1: "15", y1: "12", x2: "3", y2: "12" })] }), !collapsed && '로그인'] })) })] })] }));
48
+ }
49
+ export default AppSidebar;
@@ -1,3 +1,4 @@
1
1
  export * from './StickyNav';
2
2
  export * from './AppNavbar';
3
+ export * from './AppSidebar';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/navigation/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/navigation/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
@@ -1,2 +1,3 @@
1
1
  export * from './StickyNav';
2
2
  export * from './AppNavbar';
3
+ export * from './AppSidebar';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonhoseong/mfa-lib",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "MFA 공통 라이브러리 - KOMCA 패턴",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",