@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.
Files changed (41) hide show
  1. package/README.md +181 -0
  2. package/package.json +18 -5
  3. package/src/global.d.ts +20 -0
  4. package/src/index.ts +3 -0
  5. package/src/infrastructure/error/ErrorBoundary.tsx +258 -0
  6. package/src/infrastructure/error/ErrorDisplay.tsx +346 -0
  7. package/src/infrastructure/error/SuspenseWrapper.tsx +134 -0
  8. package/src/infrastructure/error/index.ts +5 -0
  9. package/src/infrastructure/performance/index.ts +6 -0
  10. package/src/infrastructure/performance/useLazyLoading.ts +342 -0
  11. package/src/infrastructure/performance/useMemoryOptimization.ts +293 -0
  12. package/src/infrastructure/performance/usePerformanceMonitor.ts +158 -0
  13. package/src/infrastructure/security/index.ts +10 -0
  14. package/src/infrastructure/security/security-config.ts +171 -0
  15. package/src/infrastructure/security/useFormValidation.ts +216 -0
  16. package/src/infrastructure/security/validation.ts +242 -0
  17. package/src/infrastructure/utils/cn.util.ts +7 -7
  18. package/src/infrastructure/utils/index.ts +1 -1
  19. package/src/presentation/atoms/Input.tsx +1 -1
  20. package/src/presentation/atoms/Slider.tsx +1 -1
  21. package/src/presentation/atoms/Text.tsx +1 -1
  22. package/src/presentation/atoms/Tooltip.tsx +2 -2
  23. package/src/presentation/hooks/index.ts +2 -1
  24. package/src/presentation/hooks/useClickOutside.ts +1 -1
  25. package/src/presentation/molecules/CheckboxGroup.tsx +1 -1
  26. package/src/presentation/molecules/FormField.tsx +2 -2
  27. package/src/presentation/molecules/InputGroup.tsx +1 -1
  28. package/src/presentation/molecules/RadioGroup.tsx +1 -1
  29. package/src/presentation/organisms/Alert.tsx +1 -1
  30. package/src/presentation/organisms/Card.tsx +1 -1
  31. package/src/presentation/organisms/Footer.tsx +99 -0
  32. package/src/presentation/organisms/Navbar.tsx +1 -1
  33. package/src/presentation/organisms/Table.tsx +1 -1
  34. package/src/presentation/organisms/index.ts +3 -0
  35. package/src/presentation/templates/Form.tsx +2 -2
  36. package/src/presentation/templates/List.tsx +1 -1
  37. package/src/presentation/templates/PageHeader.tsx +78 -0
  38. package/src/presentation/templates/PageLayout.tsx +38 -0
  39. package/src/presentation/templates/ProjectSkeleton.tsx +35 -0
  40. package/src/presentation/templates/Section.tsx +1 -1
  41. 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';