@sonhoseong/mfa-lib 1.0.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.
Files changed (98) hide show
  1. package/README.md +172 -0
  2. package/dist/components/button/ScrollTopButton.d.ts +5 -0
  3. package/dist/components/button/ScrollTopButton.d.ts.map +1 -0
  4. package/dist/components/button/ScrollTopButton.js +28 -0
  5. package/dist/components/button/index.d.ts +3 -0
  6. package/dist/components/button/index.d.ts.map +1 -0
  7. package/dist/components/button/index.js +2 -0
  8. package/dist/components/button/types/index.d.ts +10 -0
  9. package/dist/components/button/types/index.d.ts.map +1 -0
  10. package/dist/components/button/types/index.js +1 -0
  11. package/dist/components/error/ErrorBoundary.d.ts +25 -0
  12. package/dist/components/error/ErrorBoundary.d.ts.map +1 -0
  13. package/dist/components/error/ErrorBoundary.js +130 -0
  14. package/dist/components/error/index.d.ts +2 -0
  15. package/dist/components/error/index.d.ts.map +1 -0
  16. package/dist/components/error/index.js +1 -0
  17. package/dist/components/index.d.ts +6 -0
  18. package/dist/components/index.d.ts.map +1 -0
  19. package/dist/components/index.js +10 -0
  20. package/dist/components/loading/GlobalLoading.d.ts +12 -0
  21. package/dist/components/loading/GlobalLoading.d.ts.map +1 -0
  22. package/dist/components/loading/GlobalLoading.js +84 -0
  23. package/dist/components/loading/index.d.ts +2 -0
  24. package/dist/components/loading/index.d.ts.map +1 -0
  25. package/dist/components/loading/index.js +1 -0
  26. package/dist/components/modal/ModalContainer.d.ts +8 -0
  27. package/dist/components/modal/ModalContainer.d.ts.map +1 -0
  28. package/dist/components/modal/ModalContainer.js +161 -0
  29. package/dist/components/modal/ModalContext.d.ts +43 -0
  30. package/dist/components/modal/ModalContext.d.ts.map +1 -0
  31. package/dist/components/modal/ModalContext.js +78 -0
  32. package/dist/components/modal/index.d.ts +4 -0
  33. package/dist/components/modal/index.d.ts.map +1 -0
  34. package/dist/components/modal/index.js +2 -0
  35. package/dist/components/toast/ToastContainer.d.ts +12 -0
  36. package/dist/components/toast/ToastContainer.d.ts.map +1 -0
  37. package/dist/components/toast/ToastContainer.js +125 -0
  38. package/dist/components/toast/ToastContext.d.ts +40 -0
  39. package/dist/components/toast/ToastContext.d.ts.map +1 -0
  40. package/dist/components/toast/ToastContext.js +56 -0
  41. package/dist/components/toast/index.d.ts +4 -0
  42. package/dist/components/toast/index.d.ts.map +1 -0
  43. package/dist/components/toast/index.js +2 -0
  44. package/dist/hooks/index.d.ts +11 -0
  45. package/dist/hooks/index.d.ts.map +1 -0
  46. package/dist/hooks/index.js +17 -0
  47. package/dist/hooks/use-auth.d.ts +38 -0
  48. package/dist/hooks/use-auth.d.ts.map +1 -0
  49. package/dist/hooks/use-auth.js +117 -0
  50. package/dist/hooks/use-error-notification.d.ts +29 -0
  51. package/dist/hooks/use-error-notification.d.ts.map +1 -0
  52. package/dist/hooks/use-error-notification.js +91 -0
  53. package/dist/hooks/use-global-loading.d.ts +16 -0
  54. package/dist/hooks/use-global-loading.d.ts.map +1 -0
  55. package/dist/hooks/use-global-loading.js +72 -0
  56. package/dist/hooks/use-initialize.d.ts +26 -0
  57. package/dist/hooks/use-initialize.d.ts.map +1 -0
  58. package/dist/hooks/use-initialize.js +104 -0
  59. package/dist/hooks/use-modal.d.ts +39 -0
  60. package/dist/hooks/use-modal.d.ts.map +1 -0
  61. package/dist/hooks/use-modal.js +83 -0
  62. package/dist/hooks/use-navigate.d.ts +34 -0
  63. package/dist/hooks/use-navigate.d.ts.map +1 -0
  64. package/dist/hooks/use-navigate.js +89 -0
  65. package/dist/hooks/use-track-history.d.ts +33 -0
  66. package/dist/hooks/use-track-history.d.ts.map +1 -0
  67. package/dist/hooks/use-track-history.js +150 -0
  68. package/dist/index.d.ts +12 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +17 -0
  71. package/dist/network/axios-factory.d.ts +61 -0
  72. package/dist/network/axios-factory.d.ts.map +1 -0
  73. package/dist/network/axios-factory.js +119 -0
  74. package/dist/network/index.d.ts +5 -0
  75. package/dist/network/index.d.ts.map +1 -0
  76. package/dist/network/index.js +4 -0
  77. package/dist/network/supabase-axios.d.ts +62 -0
  78. package/dist/network/supabase-axios.d.ts.map +1 -0
  79. package/dist/network/supabase-axios.js +74 -0
  80. package/dist/store/index.d.ts +2 -0
  81. package/dist/store/index.d.ts.map +1 -0
  82. package/dist/store/index.js +1 -0
  83. package/dist/store/store-access.d.ts +48 -0
  84. package/dist/store/store-access.d.ts.map +1 -0
  85. package/dist/store/store-access.js +131 -0
  86. package/dist/types/index.d.ts +58 -0
  87. package/dist/types/index.d.ts.map +1 -0
  88. package/dist/types/index.js +5 -0
  89. package/dist/types/service.d.ts +17 -0
  90. package/dist/types/service.d.ts.map +1 -0
  91. package/dist/types/service.js +31 -0
  92. package/dist/utils/index.d.ts +2 -0
  93. package/dist/utils/index.d.ts.map +1 -0
  94. package/dist/utils/index.js +1 -0
  95. package/dist/utils/storage.d.ts +27 -0
  96. package/dist/utils/storage.d.ts.map +1 -0
  97. package/dist/utils/storage.js +78 -0
  98. package/package.json +27 -0
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # @mfa-portfolio/lib
2
+
3
+ MFA Portfolio 공통 라이브러리 - KOMCA 패턴 기반 마이크로프론트엔드 유틸리티
4
+
5
+ ## 설치
6
+
7
+ ```bash
8
+ npm install @mfa-portfolio/lib
9
+ # or
10
+ yarn add @mfa-portfolio/lib
11
+ # or
12
+ pnpm add @mfa-portfolio/lib
13
+ ```
14
+
15
+ ## 기능
16
+
17
+ ### Components
18
+
19
+ - **ScrollTopButton** - 스크롤 최상단 이동 버튼
20
+ - **GlobalLoading** - 전역 로딩 스피너
21
+ - **ToastContainer/ToastProvider** - 토스트 알림 시스템
22
+ - **ModalContainer/ModalProvider** - 모달 시스템
23
+ - **ErrorBoundary** - 에러 바운더리
24
+
25
+ ### Hooks
26
+
27
+ - **useAuth** - 인증 관련 훅 (로그인, 로그아웃, 토큰 갱신)
28
+ - **useToast** - 토스트 알림 훅
29
+ - **useModalContext** - 모달 훅
30
+ - **useGlobalLoading** - 전역 로딩 상태 훅
31
+ - **useKomcaNavigate** - 네비게이션 훅
32
+
33
+ ### Store
34
+
35
+ - **getHostStore** - Host Redux Store 접근
36
+ - **getAccessToken** - 액세스 토큰 조회
37
+ - **getCurrentUser** - 현재 사용자 조회
38
+ - **dispatchToHost** - Host Store에 액션 디스패치
39
+
40
+ ### Network
41
+
42
+ - **AxiosClientFactory** - Axios 클라이언트 팩토리 (토큰 갱신 포함)
43
+ - **initAxiosFactory** - Axios 팩토리 초기화
44
+
45
+ ### Utils
46
+
47
+ - **storage** - localStorage 유틸리티
48
+
49
+ ## 사용법
50
+
51
+ ### 기본 설정 (Host App)
52
+
53
+ ```tsx
54
+ import { Provider } from 'react-redux';
55
+ import { ToastProvider, ModalProvider } from '@mfa-portfolio/lib';
56
+ import { store } from './store';
57
+
58
+ // Store를 전역에 노출 (MFA 패턴)
59
+ window.__REDUX_STORE__ = store;
60
+
61
+ function App() {
62
+ return (
63
+ <Provider store={store}>
64
+ <ToastProvider>
65
+ <ModalProvider>
66
+ <YourApp />
67
+ </ModalProvider>
68
+ </ToastProvider>
69
+ </Provider>
70
+ );
71
+ }
72
+ ```
73
+
74
+ ### Toast 사용
75
+
76
+ ```tsx
77
+ import { useToast } from '@mfa-portfolio/lib';
78
+
79
+ function MyComponent() {
80
+ const { success, error, warning, info } = useToast();
81
+
82
+ const handleClick = () => {
83
+ success('저장되었습니다!');
84
+ error('오류가 발생했습니다.');
85
+ };
86
+
87
+ return <button onClick={handleClick}>알림</button>;
88
+ }
89
+ ```
90
+
91
+ ### Modal 사용
92
+
93
+ ```tsx
94
+ import { useModalContext } from '@mfa-portfolio/lib';
95
+
96
+ function MyComponent() {
97
+ const { alert, confirm } = useModalContext();
98
+
99
+ const handleDelete = async () => {
100
+ const result = await confirm('정말 삭제하시겠습니까?');
101
+ if (result) {
102
+ // 삭제 로직
103
+ }
104
+ };
105
+
106
+ return <button onClick={handleDelete}>삭제</button>;
107
+ }
108
+ ```
109
+
110
+ ### Axios 클라이언트 설정
111
+
112
+ ```tsx
113
+ import { AxiosClientFactory, initAxiosFactory, getAccessToken } from '@mfa-portfolio/lib';
114
+
115
+ // 팩토리 초기화
116
+ initAxiosFactory({
117
+ getAccessToken: () => getAccessToken(),
118
+ setAccessToken: (token) => store.dispatch(setAccessToken(token)),
119
+ refreshToken: async () => {
120
+ // 토큰 갱신 로직
121
+ const newToken = await refreshTokenApi();
122
+ return newToken;
123
+ },
124
+ onUnauthorized: () => {
125
+ // 로그아웃 처리
126
+ store.dispatch(logout());
127
+ },
128
+ });
129
+
130
+ // 클라이언트 생성
131
+ const apiClient = AxiosClientFactory.createClient({
132
+ hostUrl: 'https://api.example.com',
133
+ basePath: '/api/v1',
134
+ });
135
+ ```
136
+
137
+ ### Remote App에서 Host Store 접근
138
+
139
+ ```tsx
140
+ import { getHostStore, getCurrentUser, getAccessToken } from '@mfa-portfolio/lib';
141
+
142
+ function RemoteComponent() {
143
+ const user = getCurrentUser();
144
+ const token = getAccessToken();
145
+
146
+ return <div>안녕하세요, {user?.name}님!</div>;
147
+ }
148
+ ```
149
+
150
+ ## MFA 아키텍처
151
+
152
+ 이 라이브러리는 Module Federation 기반 마이크로프론트엔드 아키텍처를 위해 설계되었습니다.
153
+
154
+ ```
155
+ ┌─────────────────────────────────────────┐
156
+ │ Host App │
157
+ │ ┌─────────────────────────────────┐ │
158
+ │ │ Redux Store (전역 노출) │ │
159
+ │ │ window.__REDUX_STORE__ │ │
160
+ │ └─────────────────────────────────┘ │
161
+ │ │ │
162
+ │ ┌───────────────┼───────────────┐ │
163
+ │ │ │ │ │
164
+ │ ▼ ▼ ▼ │
165
+ │ Remote1 Remote2 Remote3 │
166
+ │ (Resume) (Blog) (Portfolio) │
167
+ └─────────────────────────────────────────┘
168
+ ```
169
+
170
+ ## License
171
+
172
+ MIT
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import { ScrollTopButtonProps } from './types';
3
+ declare const ScrollTopButton: React.FC<ScrollTopButtonProps>;
4
+ export { ScrollTopButton };
5
+ //# sourceMappingURL=ScrollTopButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScrollTopButton.d.ts","sourceRoot":"","sources":["../../../src/components/button/ScrollTopButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2C,MAAM,OAAO,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE/C,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA2DnD,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useCallback } from 'react';
3
+ const ScrollTopButton = ({ className = '', variant = 'primary', size = 'md', threshold = 100, title = '맨 위로', }) => {
4
+ const [isVisible, setIsVisible] = useState(false);
5
+ useEffect(() => {
6
+ const handleScroll = () => {
7
+ setIsVisible(window.scrollY > threshold);
8
+ };
9
+ window.addEventListener('scroll', handleScroll);
10
+ handleScroll();
11
+ return () => window.removeEventListener('scroll', handleScroll);
12
+ }, [threshold]);
13
+ const scrollToTop = useCallback(() => {
14
+ window.scrollTo({ top: 0, behavior: 'smooth' });
15
+ }, []);
16
+ const sizeClasses = {
17
+ sm: 'scroll-top-btn--sm',
18
+ md: 'scroll-top-btn--md',
19
+ lg: 'scroll-top-btn--lg',
20
+ };
21
+ const variantClasses = {
22
+ primary: 'scroll-top-btn--primary',
23
+ secondary: 'scroll-top-btn--secondary',
24
+ ghost: 'scroll-top-btn--ghost',
25
+ };
26
+ return (_jsx("button", { type: "button", className: `scroll-top-btn ${sizeClasses[size]} ${variantClasses[variant]} ${isVisible ? 'visible' : ''} ${className}`, onClick: scrollToTop, title: title, "aria-label": title, children: _jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("polyline", { points: "17 11 12 6 7 11" }), _jsx("polyline", { points: "17 18 12 13 7 18" })] }) }));
27
+ };
28
+ export { ScrollTopButton };
@@ -0,0 +1,3 @@
1
+ export { ScrollTopButton } from './ScrollTopButton';
2
+ export * from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/button/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,cAAc,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { ScrollTopButton } from './ScrollTopButton';
2
+ export * from './types';
@@ -0,0 +1,10 @@
1
+ export type ScrollTopButtonVariant = 'primary' | 'secondary' | 'ghost';
2
+ export type ScrollTopButtonSize = 'sm' | 'md' | 'lg';
3
+ export interface ScrollTopButtonProps {
4
+ className?: string;
5
+ variant?: ScrollTopButtonVariant;
6
+ size?: ScrollTopButtonSize;
7
+ threshold?: number;
8
+ title?: string;
9
+ }
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/button/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * ErrorBoundary - KOMCA 패턴
3
+ * 에러 발생 시 Fallback UI 표시
4
+ */
5
+ import { Component, ErrorInfo, ReactNode } from 'react';
6
+ interface ErrorBoundaryProps {
7
+ children: ReactNode;
8
+ /** 커스텀 Fallback UI */
9
+ fallback?: ReactNode | ((error: Error, resetError: () => void) => ReactNode);
10
+ /** 에러 발생 시 콜백 */
11
+ onError?: (error: Error, errorInfo: ErrorInfo) => void;
12
+ }
13
+ interface ErrorBoundaryState {
14
+ hasError: boolean;
15
+ error: Error | null;
16
+ }
17
+ declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
18
+ constructor(props: ErrorBoundaryProps);
19
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState;
20
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
21
+ resetError: () => void;
22
+ render(): ReactNode;
23
+ }
24
+ export default ErrorBoundary;
25
+ //# sourceMappingURL=ErrorBoundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/components/error/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAc,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE/D,UAAU,kBAAkB;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,sBAAsB;IACtB,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC,CAAC;IAC7E,iBAAiB;IACjB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,IAAI,CAAC;CAC1D;AAED,UAAU,kBAAkB;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACvB;AAED,cAAM,aAAc,SAAQ,SAAS,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;gBAC7D,KAAK,EAAE,kBAAkB;IAKrC,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,kBAAkB;IAIjE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI;IAK3D,UAAU,QAAO,IAAI,CAEnB;IAEF,MAAM,IAAI,SAAS;CA6ItB;AAED,eAAe,aAAa,CAAC"}
@@ -0,0 +1,130 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * ErrorBoundary - KOMCA 패턴
4
+ * 에러 발생 시 Fallback UI 표시
5
+ */
6
+ import { Component } from 'react';
7
+ class ErrorBoundary extends Component {
8
+ constructor(props) {
9
+ super(props);
10
+ this.resetError = () => {
11
+ this.setState({ hasError: false, error: null });
12
+ };
13
+ this.state = { hasError: false, error: null };
14
+ }
15
+ static getDerivedStateFromError(error) {
16
+ return { hasError: true, error };
17
+ }
18
+ componentDidCatch(error, errorInfo) {
19
+ console.error('ErrorBoundary caught an error:', error, errorInfo);
20
+ this.props.onError?.(error, errorInfo);
21
+ }
22
+ render() {
23
+ if (this.state.hasError && this.state.error) {
24
+ // 커스텀 fallback
25
+ if (this.props.fallback) {
26
+ if (typeof this.props.fallback === 'function') {
27
+ return this.props.fallback(this.state.error, this.resetError);
28
+ }
29
+ return this.props.fallback;
30
+ }
31
+ // 기본 fallback UI
32
+ return (_jsxs("div", { className: "error-boundary-fallback", children: [_jsxs("div", { className: "error-boundary-content", children: [_jsx("div", { className: "error-boundary-icon", children: "\u26A0\uFE0F" }), _jsx("h2", { className: "error-boundary-title", children: "\uBB38\uC81C\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4" }), _jsx("p", { className: "error-boundary-message", children: this.state.error.message || '알 수 없는 오류가 발생했습니다.' }), _jsxs("div", { className: "error-boundary-actions", children: [_jsx("button", { className: "error-boundary-button primary", onClick: this.resetError, children: "\uB2E4\uC2DC \uC2DC\uB3C4" }), _jsx("button", { className: "error-boundary-button secondary", onClick: () => window.location.reload(), children: "\uC0C8\uB85C\uACE0\uCE68" })] }), process.env.NODE_ENV === 'development' && (_jsxs("details", { className: "error-boundary-details", children: [_jsx("summary", { children: "\uC0C1\uC138 \uC815\uBCF4" }), _jsx("pre", { children: this.state.error.stack })] }))] }), _jsx("style", { children: `
33
+ .error-boundary-fallback {
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ min-height: 400px;
38
+ padding: 24px;
39
+ background: #fafafa;
40
+ }
41
+
42
+ .error-boundary-content {
43
+ text-align: center;
44
+ max-width: 480px;
45
+ padding: 40px;
46
+ background: white;
47
+ border-radius: 16px;
48
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
49
+ }
50
+
51
+ .error-boundary-icon {
52
+ font-size: 48px;
53
+ margin-bottom: 16px;
54
+ }
55
+
56
+ .error-boundary-title {
57
+ margin: 0 0 12px;
58
+ font-size: 20px;
59
+ font-weight: 600;
60
+ color: #111827;
61
+ }
62
+
63
+ .error-boundary-message {
64
+ margin: 0 0 24px;
65
+ font-size: 14px;
66
+ color: #6b7280;
67
+ line-height: 1.5;
68
+ }
69
+
70
+ .error-boundary-actions {
71
+ display: flex;
72
+ gap: 12px;
73
+ justify-content: center;
74
+ }
75
+
76
+ .error-boundary-button {
77
+ padding: 10px 20px;
78
+ font-size: 14px;
79
+ font-weight: 500;
80
+ border-radius: 8px;
81
+ cursor: pointer;
82
+ transition: all 0.15s;
83
+ }
84
+
85
+ .error-boundary-button.primary {
86
+ background: #3b82f6;
87
+ color: white;
88
+ border: none;
89
+ }
90
+
91
+ .error-boundary-button.primary:hover {
92
+ background: #2563eb;
93
+ }
94
+
95
+ .error-boundary-button.secondary {
96
+ background: white;
97
+ color: #374151;
98
+ border: 1px solid #d1d5db;
99
+ }
100
+
101
+ .error-boundary-button.secondary:hover {
102
+ background: #f9fafb;
103
+ }
104
+
105
+ .error-boundary-details {
106
+ margin-top: 24px;
107
+ text-align: left;
108
+ }
109
+
110
+ .error-boundary-details summary {
111
+ cursor: pointer;
112
+ font-size: 12px;
113
+ color: #9ca3af;
114
+ }
115
+
116
+ .error-boundary-details pre {
117
+ margin-top: 8px;
118
+ padding: 12px;
119
+ font-size: 11px;
120
+ background: #f3f4f6;
121
+ border-radius: 6px;
122
+ overflow-x: auto;
123
+ color: #ef4444;
124
+ }
125
+ ` })] }));
126
+ }
127
+ return this.props.children;
128
+ }
129
+ }
130
+ export default ErrorBoundary;
@@ -0,0 +1,2 @@
1
+ export { default as ErrorBoundary } from './ErrorBoundary';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/error/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1 @@
1
+ export { default as ErrorBoundary } from './ErrorBoundary';
@@ -0,0 +1,6 @@
1
+ export * from './button';
2
+ export * from './loading';
3
+ export * from './toast';
4
+ export * from './error';
5
+ export * from './modal';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AACA,cAAc,UAAU,CAAC;AAGzB,cAAc,WAAW,CAAC;AAG1B,cAAc,SAAS,CAAC;AAGxB,cAAc,SAAS,CAAC;AAGxB,cAAc,SAAS,CAAC"}
@@ -0,0 +1,10 @@
1
+ // Button
2
+ export * from './button';
3
+ // Loading
4
+ export * from './loading';
5
+ // Toast
6
+ export * from './toast';
7
+ // Error
8
+ export * from './error';
9
+ // Modal
10
+ export * from './modal';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * GlobalLoading Component - KOMCA 패턴
3
+ * 전역 로딩 스피너 UI
4
+ */
5
+ import React from 'react';
6
+ interface GlobalLoadingProps {
7
+ /** 커스텀 로딩 메시지 */
8
+ message?: string;
9
+ }
10
+ declare const GlobalLoading: React.FC<GlobalLoadingProps>;
11
+ export default GlobalLoading;
12
+ //# sourceMappingURL=GlobalLoading.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GlobalLoading.d.ts","sourceRoot":"","sources":["../../../src/components/loading/GlobalLoading.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,UAAU,kBAAkB;IACxB,iBAAiB;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAiG/C,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useSelector } from 'react-redux';
3
+ const GlobalLoading = ({ message }) => {
4
+ const isLoading = useSelector((state) => state.app?.isLoading);
5
+ const globalLoadingTitle = useSelector((state) => state.app?.globalLoadingTitle);
6
+ if (!isLoading)
7
+ return null;
8
+ const displayMessage = message || globalLoadingTitle || '로딩 중...';
9
+ return (_jsxs("div", { className: "global-loading-overlay", children: [_jsxs("div", { className: "global-loading-content", children: [_jsxs("div", { className: "global-loading-spinner", children: [_jsx("div", { className: "spinner-ring" }), _jsx("div", { className: "spinner-ring" }), _jsx("div", { className: "spinner-ring" })] }), displayMessage && (_jsx("p", { className: "global-loading-message", children: displayMessage }))] }), _jsx("style", { children: `
10
+ .global-loading-overlay {
11
+ position: fixed;
12
+ top: 0;
13
+ left: 0;
14
+ right: 0;
15
+ bottom: 0;
16
+ background: rgba(0, 0, 0, 0.5);
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ z-index: 9999;
21
+ backdrop-filter: blur(2px);
22
+ }
23
+
24
+ .global-loading-content {
25
+ display: flex;
26
+ flex-direction: column;
27
+ align-items: center;
28
+ gap: 16px;
29
+ padding: 32px 48px;
30
+ background: white;
31
+ border-radius: 12px;
32
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
33
+ }
34
+
35
+ .global-loading-spinner {
36
+ position: relative;
37
+ width: 48px;
38
+ height: 48px;
39
+ }
40
+
41
+ .spinner-ring {
42
+ position: absolute;
43
+ width: 100%;
44
+ height: 100%;
45
+ border: 3px solid transparent;
46
+ border-top-color: #3b82f6;
47
+ border-radius: 50%;
48
+ animation: spin 1s linear infinite;
49
+ }
50
+
51
+ .spinner-ring:nth-child(2) {
52
+ width: 80%;
53
+ height: 80%;
54
+ top: 10%;
55
+ left: 10%;
56
+ border-top-color: #60a5fa;
57
+ animation-duration: 0.8s;
58
+ animation-direction: reverse;
59
+ }
60
+
61
+ .spinner-ring:nth-child(3) {
62
+ width: 60%;
63
+ height: 60%;
64
+ top: 20%;
65
+ left: 20%;
66
+ border-top-color: #93c5fd;
67
+ animation-duration: 0.6s;
68
+ }
69
+
70
+ @keyframes spin {
71
+ to {
72
+ transform: rotate(360deg);
73
+ }
74
+ }
75
+
76
+ .global-loading-message {
77
+ margin: 0;
78
+ font-size: 14px;
79
+ color: #374151;
80
+ font-weight: 500;
81
+ }
82
+ ` })] }));
83
+ };
84
+ export default GlobalLoading;
@@ -0,0 +1,2 @@
1
+ export { default as GlobalLoading } from './GlobalLoading';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/loading/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1 @@
1
+ export { default as GlobalLoading } from './GlobalLoading';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Modal Container - KOMCA 패턴
3
+ * 모달 UI 렌더링
4
+ */
5
+ import React from 'react';
6
+ declare const ModalContainer: React.FC;
7
+ export default ModalContainer;
8
+ //# sourceMappingURL=ModalContainer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModalContainer.d.ts","sourceRoot":"","sources":["../../../src/components/modal/ModalContainer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAoB,MAAM,OAAO,CAAC;AAGzC,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EA6M3B,CAAC;AAEF,eAAe,cAAc,CAAC"}