@umituz/web-design-system 1.0.4 → 1.3.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 +181 -0
- package/package.json +18 -5
- package/src/global.d.ts +20 -0
- package/src/index.ts +3 -0
- package/src/infrastructure/error/ErrorBoundary.tsx +258 -0
- package/src/infrastructure/error/ErrorDisplay.tsx +346 -0
- package/src/infrastructure/error/SuspenseWrapper.tsx +134 -0
- package/src/infrastructure/error/index.ts +5 -0
- package/src/infrastructure/performance/index.ts +6 -0
- package/src/infrastructure/performance/useLazyLoading.ts +342 -0
- package/src/infrastructure/performance/useMemoryOptimization.ts +293 -0
- package/src/infrastructure/performance/usePerformanceMonitor.ts +158 -0
- package/src/infrastructure/security/index.ts +10 -0
- package/src/infrastructure/security/security-config.ts +171 -0
- package/src/infrastructure/security/useFormValidation.ts +216 -0
- package/src/infrastructure/security/validation.ts +242 -0
- package/src/infrastructure/utils/cn.util.ts +7 -7
- package/src/infrastructure/utils/index.ts +1 -1
- package/src/presentation/atoms/Input.tsx +1 -1
- package/src/presentation/atoms/Slider.tsx +1 -1
- package/src/presentation/atoms/Text.tsx +1 -1
- package/src/presentation/atoms/Tooltip.tsx +2 -2
- package/src/presentation/hooks/index.ts +2 -1
- package/src/presentation/hooks/useClickOutside.ts +1 -1
- package/src/presentation/molecules/CheckboxGroup.tsx +1 -1
- package/src/presentation/molecules/FormField.tsx +2 -2
- package/src/presentation/molecules/InputGroup.tsx +1 -1
- package/src/presentation/molecules/RadioGroup.tsx +1 -1
- package/src/presentation/organisms/Alert.tsx +1 -1
- package/src/presentation/organisms/Card.tsx +1 -1
- package/src/presentation/organisms/Footer.tsx +99 -0
- package/src/presentation/organisms/Navbar.tsx +1 -1
- package/src/presentation/organisms/Table.tsx +1 -1
- package/src/presentation/organisms/index.ts +3 -0
- package/src/presentation/templates/Form.tsx +2 -2
- package/src/presentation/templates/List.tsx +1 -1
- package/src/presentation/templates/PageHeader.tsx +78 -0
- package/src/presentation/templates/PageLayout.tsx +38 -0
- package/src/presentation/templates/ProjectSkeleton.tsx +35 -0
- package/src/presentation/templates/Section.tsx +1 -1
- package/src/presentation/templates/index.ts +9 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ErrorDisplayProps {
|
|
4
|
+
error?: Error | string | null;
|
|
5
|
+
title?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
variant?: 'default' | 'minimal' | 'card' | 'inline';
|
|
8
|
+
severity?: 'error' | 'warning' | 'info';
|
|
9
|
+
showDetails?: boolean;
|
|
10
|
+
showRetry?: boolean;
|
|
11
|
+
showHome?: boolean;
|
|
12
|
+
onRetry?: () => void;
|
|
13
|
+
onHome?: () => void;
|
|
14
|
+
className?: string;
|
|
15
|
+
errorId?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const severityConfig = {
|
|
19
|
+
error: {
|
|
20
|
+
icon: '❌',
|
|
21
|
+
color: '#dc2626',
|
|
22
|
+
bgColor: '#fef2f2',
|
|
23
|
+
borderColor: '#fecaca'
|
|
24
|
+
},
|
|
25
|
+
warning: {
|
|
26
|
+
icon: '⚠️',
|
|
27
|
+
color: '#d97706',
|
|
28
|
+
bgColor: '#fffbeb',
|
|
29
|
+
borderColor: '#fde68a'
|
|
30
|
+
},
|
|
31
|
+
info: {
|
|
32
|
+
icon: 'ℹ️',
|
|
33
|
+
color: '#2563eb',
|
|
34
|
+
bgColor: '#eff6ff',
|
|
35
|
+
borderColor: '#bfdbfe'
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const ErrorDisplay: React.FC<ErrorDisplayProps> = ({
|
|
40
|
+
error,
|
|
41
|
+
title = 'Something went wrong',
|
|
42
|
+
description = 'An unexpected error occurred. Please try again.',
|
|
43
|
+
variant = 'default',
|
|
44
|
+
severity = 'error',
|
|
45
|
+
showDetails = false,
|
|
46
|
+
showRetry = true,
|
|
47
|
+
showHome = false,
|
|
48
|
+
onRetry,
|
|
49
|
+
onHome,
|
|
50
|
+
className = '',
|
|
51
|
+
errorId
|
|
52
|
+
}) => {
|
|
53
|
+
const config = severityConfig[severity];
|
|
54
|
+
|
|
55
|
+
const errorMessage = error instanceof Error ? error.message : (error || 'Unknown error');
|
|
56
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
57
|
+
|
|
58
|
+
if (variant === 'minimal') {
|
|
59
|
+
return (
|
|
60
|
+
<div className={className} style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '14px', color: config.color }}>
|
|
61
|
+
<span>{config.icon}</span>
|
|
62
|
+
<span>{errorMessage}</span>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (variant === 'inline') {
|
|
68
|
+
return (
|
|
69
|
+
<div className={className} style={{
|
|
70
|
+
padding: '12px 16px',
|
|
71
|
+
background: config.bgColor,
|
|
72
|
+
border: `1px solid ${config.borderColor}`,
|
|
73
|
+
borderRadius: '6px'
|
|
74
|
+
}}>
|
|
75
|
+
<span style={{ marginRight: '8px' }}>{config.icon}</span>
|
|
76
|
+
<span style={{ fontWeight: 500 }}>{title}:</span> {errorMessage}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (variant === 'card') {
|
|
82
|
+
return (
|
|
83
|
+
<div className={className} style={{
|
|
84
|
+
background: 'white',
|
|
85
|
+
borderRadius: '8px',
|
|
86
|
+
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
|
|
87
|
+
border: 'none'
|
|
88
|
+
}}>
|
|
89
|
+
<div style={{ padding: '24px' }}>
|
|
90
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
|
|
91
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
|
92
|
+
<div style={{
|
|
93
|
+
width: '48px',
|
|
94
|
+
height: '48px',
|
|
95
|
+
borderRadius: '50%',
|
|
96
|
+
display: 'flex',
|
|
97
|
+
alignItems: 'center',
|
|
98
|
+
justifyContent: 'center',
|
|
99
|
+
background: config.bgColor,
|
|
100
|
+
fontSize: '24px'
|
|
101
|
+
}}>
|
|
102
|
+
{config.icon}
|
|
103
|
+
</div>
|
|
104
|
+
<div>
|
|
105
|
+
<h3 style={{ color: config.color, margin: 0, fontSize: '18px', fontWeight: 600 }}>
|
|
106
|
+
{title}
|
|
107
|
+
</h3>
|
|
108
|
+
<p style={{ color: '#666', margin: 0, fontSize: '14px' }}>
|
|
109
|
+
{description}
|
|
110
|
+
</p>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
{errorId && (
|
|
114
|
+
<span style={{
|
|
115
|
+
background: severity === 'error' ? '#dc2626' : '#6b7280',
|
|
116
|
+
color: 'white',
|
|
117
|
+
padding: '4px 8px',
|
|
118
|
+
borderRadius: '4px',
|
|
119
|
+
fontSize: '12px',
|
|
120
|
+
fontWeight: 500
|
|
121
|
+
}}>
|
|
122
|
+
ID: {errorId.slice(-8)}
|
|
123
|
+
</span>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
|
128
|
+
{showDetails && errorStack && (
|
|
129
|
+
<div style={{ padding: '16px', background: '#f5f5f5', borderRadius: '8px' }}>
|
|
130
|
+
<h4 style={{ fontWeight: 600, fontSize: '14px', marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
131
|
+
🐛 Error Details
|
|
132
|
+
</h4>
|
|
133
|
+
<pre style={{ fontSize: '12px', color: '#666', overflow: 'auto', maxHeight: '128px' }}>
|
|
134
|
+
{errorStack}
|
|
135
|
+
</pre>
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
|
|
139
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
|
140
|
+
{showRetry && onRetry && (
|
|
141
|
+
<button
|
|
142
|
+
onClick={onRetry}
|
|
143
|
+
style={{
|
|
144
|
+
padding: '8px 16px',
|
|
145
|
+
background: 'white',
|
|
146
|
+
border: '1px solid #d1d5db',
|
|
147
|
+
borderRadius: '6px',
|
|
148
|
+
fontSize: '14px',
|
|
149
|
+
fontWeight: 500,
|
|
150
|
+
cursor: 'pointer',
|
|
151
|
+
display: 'flex',
|
|
152
|
+
alignItems: 'center',
|
|
153
|
+
gap: '8px'
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
🔄 Try Again
|
|
157
|
+
</button>
|
|
158
|
+
)}
|
|
159
|
+
<button
|
|
160
|
+
onClick={() => window.location.reload()}
|
|
161
|
+
style={{
|
|
162
|
+
padding: '8px 16px',
|
|
163
|
+
background: '#3b82f6',
|
|
164
|
+
color: 'white',
|
|
165
|
+
border: 'none',
|
|
166
|
+
borderRadius: '6px',
|
|
167
|
+
fontSize: '14px',
|
|
168
|
+
fontWeight: 500,
|
|
169
|
+
cursor: 'pointer',
|
|
170
|
+
display: 'flex',
|
|
171
|
+
alignItems: 'center',
|
|
172
|
+
gap: '8px'
|
|
173
|
+
}}
|
|
174
|
+
>
|
|
175
|
+
🔄 Refresh Page
|
|
176
|
+
</button>
|
|
177
|
+
{showHome && onHome && (
|
|
178
|
+
<button
|
|
179
|
+
onClick={onHome}
|
|
180
|
+
style={{
|
|
181
|
+
padding: '8px 16px',
|
|
182
|
+
background: '#6b7280',
|
|
183
|
+
color: 'white',
|
|
184
|
+
border: 'none',
|
|
185
|
+
borderRadius: '6px',
|
|
186
|
+
fontSize: '14px',
|
|
187
|
+
fontWeight: 500,
|
|
188
|
+
cursor: 'pointer',
|
|
189
|
+
display: 'flex',
|
|
190
|
+
alignItems: 'center',
|
|
191
|
+
gap: '8px'
|
|
192
|
+
}}
|
|
193
|
+
>
|
|
194
|
+
🏠 Go Home
|
|
195
|
+
</button>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Default variant
|
|
205
|
+
return (
|
|
206
|
+
<div className={className} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px' }}>
|
|
207
|
+
<div style={{
|
|
208
|
+
width: '100%',
|
|
209
|
+
maxWidth: '448px',
|
|
210
|
+
background: 'white',
|
|
211
|
+
borderRadius: '8px',
|
|
212
|
+
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
|
|
213
|
+
overflow: 'hidden'
|
|
214
|
+
}}>
|
|
215
|
+
<div style={{ padding: '24px' }}>
|
|
216
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '16px' }}>
|
|
217
|
+
<div style={{
|
|
218
|
+
width: '40px',
|
|
219
|
+
height: '40px',
|
|
220
|
+
borderRadius: '50%',
|
|
221
|
+
display: 'flex',
|
|
222
|
+
alignItems: 'center',
|
|
223
|
+
justifyContent: 'center',
|
|
224
|
+
background: config.bgColor,
|
|
225
|
+
fontSize: '20px'
|
|
226
|
+
}}>
|
|
227
|
+
{config.icon}
|
|
228
|
+
</div>
|
|
229
|
+
<div>
|
|
230
|
+
<h3 style={{ color: config.color, margin: 0, fontSize: '16px', fontWeight: 600 }}>
|
|
231
|
+
{title}
|
|
232
|
+
</h3>
|
|
233
|
+
<p style={{ color: '#666', margin: 0, fontSize: '14px' }}>
|
|
234
|
+
{description}
|
|
235
|
+
</p>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div style={{ padding: '0 24px 24px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
241
|
+
<p style={{ fontSize: '14px', color: '#666', margin: 0 }}>{errorMessage}</p>
|
|
242
|
+
<div style={{ display: 'flex', gap: '8px' }}>
|
|
243
|
+
{showRetry && onRetry && (
|
|
244
|
+
<button
|
|
245
|
+
onClick={onRetry}
|
|
246
|
+
style={{
|
|
247
|
+
padding: '8px 16px',
|
|
248
|
+
background: 'white',
|
|
249
|
+
border: '1px solid #d1d5db',
|
|
250
|
+
borderRadius: '6px',
|
|
251
|
+
fontSize: '14px',
|
|
252
|
+
fontWeight: 500,
|
|
253
|
+
cursor: 'pointer',
|
|
254
|
+
display: 'flex',
|
|
255
|
+
alignItems: 'center',
|
|
256
|
+
gap: '8px'
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
🔄 Retry
|
|
260
|
+
</button>
|
|
261
|
+
)}
|
|
262
|
+
{showHome && onHome && (
|
|
263
|
+
<button
|
|
264
|
+
onClick={onHome}
|
|
265
|
+
style={{
|
|
266
|
+
padding: '8px 16px',
|
|
267
|
+
background: '#6b7280',
|
|
268
|
+
color: 'white',
|
|
269
|
+
border: 'none',
|
|
270
|
+
borderRadius: '6px',
|
|
271
|
+
fontSize: '14px',
|
|
272
|
+
fontWeight: 500,
|
|
273
|
+
cursor: 'pointer',
|
|
274
|
+
display: 'flex',
|
|
275
|
+
alignItems: 'center',
|
|
276
|
+
gap: '8px'
|
|
277
|
+
}}
|
|
278
|
+
>
|
|
279
|
+
🏠 Home
|
|
280
|
+
</button>
|
|
281
|
+
)}
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// Specialized error components
|
|
290
|
+
export const NetworkError: React.FC<{ onRetry?: () => void; className?: string }> = ({
|
|
291
|
+
onRetry,
|
|
292
|
+
className
|
|
293
|
+
}) => (
|
|
294
|
+
<ErrorDisplay
|
|
295
|
+
title="Network Error"
|
|
296
|
+
description="Unable to connect to the server. Please check your internet connection."
|
|
297
|
+
severity="warning"
|
|
298
|
+
showRetry={true}
|
|
299
|
+
onRetry={onRetry}
|
|
300
|
+
className={className}
|
|
301
|
+
/>
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
export const NotFoundError: React.FC<{ onHome?: () => void; className?: string }> = ({
|
|
305
|
+
onHome,
|
|
306
|
+
className
|
|
307
|
+
}) => (
|
|
308
|
+
<ErrorDisplay
|
|
309
|
+
title="Page Not Found"
|
|
310
|
+
description="The page you're looking for doesn't exist or has been moved."
|
|
311
|
+
severity="info"
|
|
312
|
+
showHome={true}
|
|
313
|
+
showRetry={false}
|
|
314
|
+
onHome={onHome}
|
|
315
|
+
className={className}
|
|
316
|
+
/>
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
export const PermissionError: React.FC<{ className?: string }> = ({ className }) => (
|
|
320
|
+
<ErrorDisplay
|
|
321
|
+
title="Access Denied"
|
|
322
|
+
description="You don't have permission to access this resource."
|
|
323
|
+
severity="error"
|
|
324
|
+
showRetry={false}
|
|
325
|
+
showHome={true}
|
|
326
|
+
className={className}
|
|
327
|
+
/>
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
export const ApiError: React.FC<{
|
|
331
|
+
error?: Error | string;
|
|
332
|
+
onRetry?: () => void;
|
|
333
|
+
className?: string
|
|
334
|
+
}> = ({ error, onRetry, className }) => (
|
|
335
|
+
<ErrorDisplay
|
|
336
|
+
error={error}
|
|
337
|
+
title="API Error"
|
|
338
|
+
description="Failed to load data from the server."
|
|
339
|
+
severity="error"
|
|
340
|
+
showRetry={true}
|
|
341
|
+
onRetry={onRetry}
|
|
342
|
+
className={className}
|
|
343
|
+
/>
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
export default ErrorDisplay;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import React, { Suspense } from 'react';
|
|
2
|
+
import { ErrorBoundary } from './ErrorBoundary';
|
|
3
|
+
|
|
4
|
+
export interface SuspenseWrapperProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
fallback?: React.ReactNode;
|
|
7
|
+
errorFallback?: React.ReactNode;
|
|
8
|
+
loadingText?: string;
|
|
9
|
+
variant?: 'default' | 'minimal' | 'card';
|
|
10
|
+
className?: string;
|
|
11
|
+
style?: React.CSSProperties;
|
|
12
|
+
showErrorBoundary?: boolean;
|
|
13
|
+
errorBoundaryLevel?: 'page' | 'component' | 'feature';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const DefaultLoadingSpinner: React.FC<{ text?: string; className?: string }> = ({ text, className }) => (
|
|
17
|
+
<div style={{
|
|
18
|
+
display: 'flex',
|
|
19
|
+
flexDirection: 'column',
|
|
20
|
+
alignItems: 'center',
|
|
21
|
+
justifyContent: 'center',
|
|
22
|
+
gap: '16px',
|
|
23
|
+
padding: '32px',
|
|
24
|
+
...({ className } as any)
|
|
25
|
+
}}>
|
|
26
|
+
<div style={{
|
|
27
|
+
width: '40px',
|
|
28
|
+
height: '40px',
|
|
29
|
+
border: '4px solid #e5e7eb',
|
|
30
|
+
borderTopColor: '#3b82f6',
|
|
31
|
+
borderRadius: '50%',
|
|
32
|
+
animation: 'spin 1s linear infinite'
|
|
33
|
+
}} />
|
|
34
|
+
{text && <p style={{ fontSize: '14px', color: '#666' }}>{text}</p>}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export const SuspenseWrapper: React.FC<SuspenseWrapperProps> = ({
|
|
39
|
+
children,
|
|
40
|
+
fallback,
|
|
41
|
+
errorFallback,
|
|
42
|
+
loadingText = 'Loading...',
|
|
43
|
+
variant = 'default',
|
|
44
|
+
className = '',
|
|
45
|
+
style,
|
|
46
|
+
showErrorBoundary = true,
|
|
47
|
+
errorBoundaryLevel = 'component'
|
|
48
|
+
}) => {
|
|
49
|
+
const defaultFallback = (
|
|
50
|
+
<DefaultLoadingSpinner
|
|
51
|
+
text={variant === 'card' ? loadingText : undefined}
|
|
52
|
+
className={className}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const suspenseContent = (
|
|
57
|
+
<Suspense fallback={fallback || defaultFallback}>
|
|
58
|
+
{children}
|
|
59
|
+
</Suspense>
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const content = showErrorBoundary ? (
|
|
63
|
+
<ErrorBoundary
|
|
64
|
+
level={errorBoundaryLevel}
|
|
65
|
+
fallback={errorFallback}
|
|
66
|
+
>
|
|
67
|
+
{suspenseContent}
|
|
68
|
+
</ErrorBoundary>
|
|
69
|
+
) : suspenseContent;
|
|
70
|
+
|
|
71
|
+
// Wrap in div if className or style is provided
|
|
72
|
+
if (className || style) {
|
|
73
|
+
return <div className={className} style={style}>{content}</div>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return content;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Specialized Suspense wrappers
|
|
80
|
+
export const PageSuspense: React.FC<{
|
|
81
|
+
children: React.ReactNode;
|
|
82
|
+
loadingText?: string
|
|
83
|
+
}> = ({ children, loadingText = 'Loading page...' }) => (
|
|
84
|
+
<SuspenseWrapper
|
|
85
|
+
variant="card"
|
|
86
|
+
loadingText={loadingText}
|
|
87
|
+
errorBoundaryLevel="page"
|
|
88
|
+
className="min-h-[400px]"
|
|
89
|
+
>
|
|
90
|
+
{children}
|
|
91
|
+
</SuspenseWrapper>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
export const ComponentSuspense: React.FC<{
|
|
95
|
+
children: React.ReactNode;
|
|
96
|
+
loadingText?: string;
|
|
97
|
+
className?: string;
|
|
98
|
+
}> = ({ children, loadingText = 'Loading component...', className }) => (
|
|
99
|
+
<SuspenseWrapper
|
|
100
|
+
variant="default"
|
|
101
|
+
loadingText={loadingText}
|
|
102
|
+
errorBoundaryLevel="component"
|
|
103
|
+
className={className}
|
|
104
|
+
>
|
|
105
|
+
{children}
|
|
106
|
+
</SuspenseWrapper>
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
export const FeatureSuspense: React.FC<{
|
|
110
|
+
children: React.ReactNode;
|
|
111
|
+
loadingText?: string
|
|
112
|
+
}> = ({ children, loadingText = 'Loading feature...' }) => (
|
|
113
|
+
<SuspenseWrapper
|
|
114
|
+
variant="card"
|
|
115
|
+
loadingText={loadingText}
|
|
116
|
+
errorBoundaryLevel="feature"
|
|
117
|
+
style={{ padding: '24px' }}
|
|
118
|
+
>
|
|
119
|
+
{children}
|
|
120
|
+
</SuspenseWrapper>
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
export const InlineSuspense: React.FC<{
|
|
124
|
+
children: React.ReactNode
|
|
125
|
+
}> = ({ children }) => (
|
|
126
|
+
<SuspenseWrapper
|
|
127
|
+
variant="minimal"
|
|
128
|
+
showErrorBoundary={false}
|
|
129
|
+
>
|
|
130
|
+
{children}
|
|
131
|
+
</SuspenseWrapper>
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
export default SuspenseWrapper;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { ErrorBoundary } from './ErrorBoundary';
|
|
2
|
+
export { ErrorDisplay, NetworkError, NotFoundError, PermissionError, ApiError } from './ErrorDisplay';
|
|
3
|
+
export type { ErrorDisplayProps } from './ErrorDisplay';
|
|
4
|
+
export { SuspenseWrapper, PageSuspense, ComponentSuspense, FeatureSuspense, InlineSuspense } from './SuspenseWrapper';
|
|
5
|
+
export type { SuspenseWrapperProps } from './SuspenseWrapper';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { usePerformanceMonitor } from './usePerformanceMonitor';
|
|
2
|
+
export { useLazyLoading } from './useLazyLoading';
|
|
3
|
+
export { useMemoryOptimization } from './useMemoryOptimization';
|
|
4
|
+
|
|
5
|
+
export type { PerformanceMetrics, PerformanceConfig } from './usePerformanceMonitor';
|
|
6
|
+
export type { MemoryOptimizationConfig, CleanupFunction } from './useMemoryOptimization';
|