@sonhoseong/mfa-lib 1.3.1 → 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.
@@ -14,13 +14,61 @@
14
14
  display: flex;
15
15
  flex-direction: column;
16
16
  z-index: 100;
17
- transition: transform 0.3s ease;
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);
18
61
  }
19
62
 
20
63
  /* Sidebar Header */
21
64
  .sidebar-header {
22
65
  padding: 20px;
23
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;
24
72
  }
25
73
 
26
74
  .sidebar-logo {
@@ -32,10 +80,15 @@
32
80
  border: none;
33
81
  border-radius: 12px;
34
82
  cursor: pointer;
35
- transition: background 0.2s ease;
83
+ transition: background 0.2s ease, padding 0.3s ease, justify-content 0.3s ease;
36
84
  width: 100%;
37
85
  }
38
86
 
87
+ .app-sidebar.collapsed .sidebar-logo {
88
+ padding: 8px;
89
+ justify-content: center;
90
+ }
91
+
39
92
  .sidebar-logo:hover {
40
93
  background: var(--color-bg-secondary, #F8FAFC);
41
94
  }
@@ -44,6 +97,9 @@
44
97
  font-size: 18px;
45
98
  font-weight: 700;
46
99
  color: var(--color-primary, #1E3A5F);
100
+ white-space: nowrap;
101
+ overflow: hidden;
102
+ transition: opacity 0.2s ease, width 0.3s ease;
47
103
  }
48
104
 
49
105
  /* Sidebar Navigation */
@@ -51,6 +107,11 @@
51
107
  flex: 1;
52
108
  overflow-y: auto;
53
109
  padding: 16px 12px;
110
+ transition: padding 0.3s ease;
111
+ }
112
+
113
+ .app-sidebar.collapsed .sidebar-nav {
114
+ padding: 16px 8px;
54
115
  }
55
116
 
56
117
  .sidebar-menu {
@@ -81,6 +142,11 @@
81
142
  text-align: left;
82
143
  }
83
144
 
145
+ .app-sidebar.collapsed .sidebar-menu-btn {
146
+ padding: 12px;
147
+ justify-content: center;
148
+ }
149
+
84
150
  .sidebar-menu-btn:hover {
85
151
  background: var(--color-bg-secondary, #F8FAFC);
86
152
  color: var(--color-primary, #1E3A5F);
@@ -138,6 +204,11 @@
138
204
  .sidebar-footer {
139
205
  padding: 16px;
140
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;
141
212
  }
142
213
 
143
214
  .sidebar-user {
@@ -147,6 +218,12 @@
147
218
  padding: 8px;
148
219
  background: var(--color-bg-secondary, #F8FAFC);
149
220
  border-radius: 12px;
221
+ transition: justify-content 0.3s ease;
222
+ }
223
+
224
+ .sidebar-user.collapsed {
225
+ justify-content: center;
226
+ padding: 8px;
150
227
  }
151
228
 
152
229
  .sidebar-user-avatar {
@@ -229,9 +306,21 @@
229
306
  background: var(--color-accent, #0EA5E9);
230
307
  }
231
308
 
309
+ .sidebar-login-btn.collapsed {
310
+ width: 48px;
311
+ height: 48px;
312
+ padding: 0;
313
+ border-radius: 12px;
314
+ }
315
+
232
316
  /* Layout with Sidebar */
233
317
  .has-sidebar {
234
318
  margin-left: 260px;
319
+ transition: margin-left 0.3s ease;
320
+ }
321
+
322
+ .has-sidebar.collapsed {
323
+ margin-left: 72px;
235
324
  }
236
325
 
237
326
  .has-sidebar .main-content {
@@ -251,4 +340,17 @@
251
340
  .has-sidebar {
252
341
  margin-left: 0;
253
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
+ }
254
356
  }
@@ -31,7 +31,11 @@ export interface AppSidebarProps {
31
31
  currentPath?: string;
32
32
  /** 커스텀 로고 */
33
33
  logo?: React.ReactNode;
34
+ /** 접힌 상태 (외부 제어) */
35
+ collapsed?: boolean;
36
+ /** 접힘 상태 변경 콜백 */
37
+ onCollapsedChange?: (collapsed: boolean) => void;
34
38
  }
35
- export declare function AppSidebar({ appName, isAuthenticated, userName, userEmail, menuItems, onLogout, onNavigate, currentPath, logo, }: AppSidebarProps): import("react/jsx-runtime").JSX.Element;
39
+ export declare function AppSidebar({ appName, isAuthenticated, userName, userEmail, menuItems, onLogout, onNavigate, currentPath, logo, collapsed: controlledCollapsed, onCollapsedChange, }: AppSidebarProps): import("react/jsx-runtime").JSX.Element;
36
40
  export default AppSidebar;
37
41
  //# sourceMappingURL=AppSidebar.d.ts.map
@@ -1 +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;CAC1B;AAED,wBAAgB,UAAU,CAAC,EACvB,OAAe,EACf,eAAuB,EACvB,QAAQ,EACR,SAAS,EACT,SAAc,EACd,QAAQ,EACR,UAAU,EACV,WAAiB,EACjB,IAAI,GACP,EAAE,eAAe,2CAyIjB;AAED,eAAe,UAAU,CAAC"}
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"}
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /**
3
3
  * AppSidebar - KOMCA 패턴
4
4
  *
@@ -6,8 +6,16 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
6
  * Remote 앱 단독 실행 시 사용
7
7
  */
8
8
  import { useState, useCallback } from 'react';
9
- export function AppSidebar({ appName = 'MFA', isAuthenticated = false, userName, userEmail, menuItems = [], onLogout, onNavigate, currentPath = '/', logo, }) {
9
+ export function AppSidebar({ appName = 'MFA', isAuthenticated = false, userName, userEmail, menuItems = [], onLogout, onNavigate, currentPath = '/', logo, collapsed: controlledCollapsed, onCollapsedChange, }) {
10
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]);
11
19
  const toggleMenu = useCallback((menuId) => {
12
20
  setExpandedMenus(prev => {
13
21
  const next = new Set(prev);
@@ -34,8 +42,8 @@ export function AppSidebar({ appName = 'MFA', isAuthenticated = false, userName,
34
42
  else if (item.path) {
35
43
  handleNavigate(item.path);
36
44
  }
37
- }, children: [item.icon && _jsx("span", { className: "sidebar-menu-icon", children: item.icon }), _jsx("span", { className: "sidebar-menu-title", children: item.title }), 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" }) }))] }), hasChildren && isExpanded && (_jsx("ul", { className: "sidebar-submenu", children: item.children.map(child => renderMenuItem(child, depth + 1)) }))] }, item.id));
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));
38
46
  };
39
- return (_jsxs("aside", { className: "app-sidebar", 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" })] })), _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('/'), 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" })] }) }), _jsx("span", { className: "sidebar-menu-title", children: "\uD648" })] }) }) })) }), _jsx("div", { className: "sidebar-footer", children: isAuthenticated ? (_jsxs("div", { className: "sidebar-user", children: [_jsx("div", { className: "sidebar-user-avatar", children: userName?.charAt(0).toUpperCase() || 'U' }), _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", onClick: () => handleNavigate('/login'), 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" })] }), "\uB85C\uADF8\uC778"] })) })] }));
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 && '로그인'] })) })] })] }));
40
48
  }
41
49
  export default AppSidebar;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonhoseong/mfa-lib",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "MFA 공통 라이브러리 - KOMCA 패턴",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",