@umituz/web-design-system 1.0.3 → 1.1.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.
package/README.md CHANGED
@@ -103,6 +103,46 @@ import type {
103
103
  } from '@umituz/web-design-system/types';
104
104
  ```
105
105
 
106
+ ### Security (Security & Validation)
107
+ ```tsx
108
+ import {
109
+ validateInput,
110
+ validateEmail,
111
+ validateUrl,
112
+ sanitizeInput,
113
+ validateFileName,
114
+ CSP_CONFIG,
115
+ SECURITY_HEADERS,
116
+ VALIDATION_CONFIGS
117
+ } from '@umituz/web-design-system/security';
118
+
119
+ import { useFormValidation, COMMON_RULES } from '@umituz/web-design-system/security';
120
+ ```
121
+
122
+ ### Performance (Performance Optimization)
123
+ ```tsx
124
+ import {
125
+ usePerformanceMonitor,
126
+ useLazyLoading,
127
+ useMemoryOptimization,
128
+ useLazyImage,
129
+ useLazyComponent,
130
+ useVirtualList
131
+ } from '@umituz/web-design-system/performance';
132
+ ```
133
+
134
+ ### Error (Error Handling & Boundaries)
135
+ ```tsx
136
+ import {
137
+ ErrorBoundary,
138
+ ErrorDisplay,
139
+ SuspenseWrapper,
140
+ NetworkError,
141
+ NotFoundError,
142
+ ApiError
143
+ } from '@umituz/web-design-system/error';
144
+ ```
145
+
106
146
  ## 🎨 Component Examples
107
147
 
108
148
  ### Button
@@ -192,6 +232,147 @@ const fontSize = fontSizes['lg']; // '1.125rem'
192
232
  const fontWeight = fontWeights['semibold']; // '600'
193
233
  ```
194
234
 
235
+ ## 🔒 Security Features
236
+
237
+ ### Input Validation
238
+ ```tsx
239
+ import { validateEmail, sanitizeInput, validateFileName } from '@umituz/web-design-system/security';
240
+
241
+ // Validate email
242
+ const result = validateEmail('user@example.com');
243
+ if (result.isValid) {
244
+ // Email is valid
245
+ }
246
+
247
+ // Sanitize user input
248
+ const clean = sanitizeInput(userInput);
249
+
250
+ // Validate file names
251
+ const fileResult = validateFileName(fileName);
252
+ ```
253
+
254
+ ### Form Validation Hook
255
+ ```tsx
256
+ import { useFormValidation, COMMON_RULES } from '@umituz/web-design-system/security';
257
+
258
+ const MyForm = () => {
259
+ const { formData, errors, updateField, validateAllFields, isFormValid } = useFormValidation(
260
+ { name: '', email: '' },
261
+ {
262
+ name: COMMON_RULES.name,
263
+ email: COMMON_RULES.email
264
+ }
265
+ );
266
+
267
+ return (
268
+ <form onSubmit={handleSubmit}>
269
+ <input
270
+ value={formData.name}
271
+ onChange={(e) => updateField('name', e.target.value)}
272
+ />
273
+ {errors.name && <span className="error">{errors.name}</span>}
274
+ </form>
275
+ );
276
+ };
277
+ ```
278
+
279
+ ## ⚡ Performance Features
280
+
281
+ ### Performance Monitoring
282
+ ```tsx
283
+ import { usePerformanceMonitor } from '@umituz/web-design-system/performance';
284
+
285
+ const MyComponent = () => {
286
+ const { metrics, getPerformanceReport } = usePerformanceMonitor({
287
+ componentName: 'MyComponent',
288
+ trackRenders: true,
289
+ trackMemory: true
290
+ });
291
+
292
+ return <div>Render time: {metrics.renderTime}ms</div>;
293
+ };
294
+ ```
295
+
296
+ ### Lazy Loading
297
+ ```tsx
298
+ import { useLazyLoading, useLazyImage } from '@umituz/web-design-system/performance';
299
+
300
+ const LazyImage = () => {
301
+ const { targetRef, imageSrc, isLoaded } = useLazyImage('/large-image.jpg');
302
+
303
+ return (
304
+ <div ref={targetRef}>
305
+ {isLoaded ? <img src={imageSrc} alt="Lazy loaded" /> : <div>Loading...</div>}
306
+ </div>
307
+ );
308
+ };
309
+ ```
310
+
311
+ ### Memory Optimization
312
+ ```tsx
313
+ import { useMemoryOptimization } from '@umituz/web-design-system/performance';
314
+
315
+ const MyComponent = () => {
316
+ const { addEventListener, setTimeout, addCleanup, getMemoryStats } = useMemoryOptimization();
317
+
318
+ useEffect(() => {
319
+ const cleanup = addEventListener(window, 'resize', handleResize);
320
+ const timerId = setTimeout(() => {}, 1000);
321
+
322
+ return () => {
323
+ cleanup();
324
+ // Automatic cleanup on unmount
325
+ };
326
+ }, []);
327
+
328
+ return <div>Memory stats: {getMemoryStats().totalTrackedItems} items</div>;
329
+ };
330
+ ```
331
+
332
+ ## 🚨 Error Handling
333
+
334
+ ### Error Boundary
335
+ ```tsx
336
+ import { ErrorBoundary } from '@umituz/web-design-system/error';
337
+
338
+ <ErrorBoundary
339
+ level="page"
340
+ showDetails={true}
341
+ onError={(error, errorInfo) => console.error(error)}
342
+ >
343
+ <MyComponent />
344
+ </ErrorBoundary>
345
+ ```
346
+
347
+ ### Error Display
348
+ ```tsx
349
+ import { ErrorDisplay, NetworkError, ApiError } from '@umituz/web-design-system/error';
350
+
351
+ <ErrorDisplay
352
+ error={error}
353
+ title="Something went wrong"
354
+ severity="error"
355
+ showRetry={true}
356
+ onRetry={retryFunction}
357
+ />
358
+
359
+ <NetworkError onRetry={retry} />
360
+ <ApiError error={apiError} onRetry={retry} />
361
+ ```
362
+
363
+ ### Suspense Wrapper
364
+ ```tsx
365
+ import { SuspenseWrapper, PageSuspense } from '@umituz/web-design-system/error';
366
+
367
+ <PageSuspense loadingText="Loading page...">
368
+ <MyLazyComponent />
369
+ </PageSuspense>
370
+
371
+ <SuspenseWrapper variant="card" errorBoundaryLevel="feature">
372
+ <AsyncComponent />
373
+ </SuspenseWrapper>
374
+ ```
375
+
195
376
  ## 📐 Atomic Design Principles
196
377
 
197
378
  This package follows Atomic Design methodology:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-design-system",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "Web Design System - Atomic Design components (Atoms, Molecules, Organisms) for React applications",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -16,6 +16,9 @@
16
16
  "./constants": "./src/infrastructure/constants/index.ts",
17
17
  "./tokens": "./src/domain/tokens/index.ts",
18
18
  "./types": "./src/domain/types/index.ts",
19
+ "./security": "./src/infrastructure/security/index.ts",
20
+ "./performance": "./src/infrastructure/performance/index.ts",
21
+ "./error": "./src/infrastructure/error/index.ts",
19
22
  "./package.json": "./package.json"
20
23
  },
21
24
  "scripts": {
@@ -34,13 +37,18 @@
34
37
  "atoms",
35
38
  "molecules",
36
39
  "organisms",
37
- "tailwind"
40
+ "tailwind",
41
+ "security",
42
+ "performance",
43
+ "error-handling",
44
+ "validation",
45
+ "optimization"
38
46
  ],
39
47
  "author": "umituz",
40
48
  "license": "MIT",
41
49
  "repository": {
42
50
  "type": "git",
43
- "url": "https://github.com/umituz/web-design-system"
51
+ "url": "git+https://github.com/umituz/web-design-system.git"
44
52
  },
45
53
  "peerDependencies": {
46
54
  "react": ">=18.0.0",
package/src/index.ts CHANGED
@@ -21,6 +21,9 @@ export * from './domain/tokens';
21
21
  export * from './domain/types';
22
22
  export * from './infrastructure/utils';
23
23
  export * from './infrastructure/constants';
24
+ export * from './infrastructure/security';
25
+ export * from './infrastructure/performance';
26
+ export * from './infrastructure/error';
24
27
  export * from './presentation/atoms';
25
28
  export * from './presentation/molecules';
26
29
  export * from './presentation/organisms';
@@ -0,0 +1,258 @@
1
+ import React, { Component, ErrorInfo, ReactNode, memo } from "react";
2
+
3
+ interface Props {
4
+ children: ReactNode;
5
+ fallback?: ReactNode;
6
+ onError?: (error: Error, errorInfo: ErrorInfo) => void;
7
+ showDetails?: boolean;
8
+ level?: 'page' | 'component' | 'feature';
9
+ }
10
+
11
+ interface State {
12
+ hasError: boolean;
13
+ error?: Error;
14
+ errorInfo?: ErrorInfo;
15
+ errorId?: string;
16
+ }
17
+
18
+ const ErrorDetails = memo(({ error, errorInfo, showDetails }: {
19
+ error: Error;
20
+ errorInfo?: ErrorInfo;
21
+ showDetails: boolean;
22
+ }) => {
23
+ if (!showDetails || !import.meta.env.DEV) return null;
24
+
25
+ return (
26
+ <div style={{ padding: '16px', background: '#f5f5f5', borderRadius: '8px', marginTop: '16px' }}>
27
+ <h4 style={{ fontWeight: 600, fontSize: '14px', marginBottom: '8px' }}>
28
+ 🐛 Error Details (Development)
29
+ </h4>
30
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
31
+ <div>
32
+ <p style={{ fontSize: '12px', fontWeight: 500, color: '#666' }}>Message:</p>
33
+ <pre style={{ fontSize: '12px', color: '#dc2626', overflow: 'auto' }}>{error.message}</pre>
34
+ </div>
35
+ {error.stack && (
36
+ <div>
37
+ <p style={{ fontSize: '12px', fontWeight: 500, color: '#666' }}>Stack:</p>
38
+ <pre style={{ fontSize: '12px', color: '#666', overflow: 'auto', maxHeight: '128px' }}>
39
+ {error.stack}
40
+ </pre>
41
+ </div>
42
+ )}
43
+ {errorInfo?.componentStack && (
44
+ <div>
45
+ <p style={{ fontSize: '12px', fontWeight: 500, color: '#666' }}>Component Stack:</p>
46
+ <pre style={{ fontSize: '12px', color: '#666', overflow: 'auto', maxHeight: '128px' }}>
47
+ {errorInfo.componentStack}
48
+ </pre>
49
+ </div>
50
+ )}
51
+ </div>
52
+ </div>
53
+ );
54
+ });
55
+
56
+ ErrorDetails.displayName = 'ErrorDetails';
57
+
58
+ export class ErrorBoundary extends Component<Props, State> {
59
+ constructor(props: Props) {
60
+ super(props);
61
+ this.state = { hasError: false };
62
+ }
63
+
64
+ static getDerivedStateFromError(error: Error): Partial<State> {
65
+ return {
66
+ hasError: true,
67
+ error,
68
+ errorId: `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
69
+ };
70
+ }
71
+
72
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
73
+ this.setState({ error, errorInfo });
74
+
75
+ this.props.onError?.(error, errorInfo);
76
+
77
+ if (import.meta.env.DEV) {
78
+ console.group('🚨 ErrorBoundary caught an error');
79
+ console.error('Error:', error);
80
+ console.error('Error Info:', errorInfo);
81
+ console.groupEnd();
82
+ }
83
+ }
84
+
85
+ private handleReset = () => {
86
+ this.setState({ hasError: false, error: undefined, errorInfo: undefined, errorId: undefined });
87
+ };
88
+
89
+ private handleGoHome = () => {
90
+ window.location.href = '/';
91
+ };
92
+
93
+ private getLevelConfig = () => {
94
+ const { level = 'component' } = this.props;
95
+
96
+ switch (level) {
97
+ case 'page':
98
+ return {
99
+ title: 'Page Error',
100
+ description: 'This page encountered an unexpected error.',
101
+ showHomeButton: true,
102
+ };
103
+ case 'feature':
104
+ return {
105
+ title: 'Feature Error',
106
+ description: 'This feature is temporarily unavailable due to an error.',
107
+ showHomeButton: false,
108
+ };
109
+ default:
110
+ return {
111
+ title: 'Component Error',
112
+ description: 'A component on this page encountered an error.',
113
+ showHomeButton: false,
114
+ };
115
+ }
116
+ };
117
+
118
+ render() {
119
+ if (this.state.hasError) {
120
+ if (this.props.fallback) {
121
+ return this.props.fallback;
122
+ }
123
+
124
+ const config = this.getLevelConfig();
125
+ const { showDetails = false } = this.props;
126
+
127
+ return (
128
+ <div style={{
129
+ minHeight: '100vh',
130
+ display: 'flex',
131
+ alignItems: 'center',
132
+ justifyContent: 'center',
133
+ padding: '16px',
134
+ background: 'linear-gradient(to bottom right, #fef2f2, #fff7ed)'
135
+ }}>
136
+ <div style={{
137
+ width: '100%',
138
+ maxWidth: '672px',
139
+ background: 'white',
140
+ borderRadius: '8px',
141
+ boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
142
+ overflow: 'hidden'
143
+ }}>
144
+ <div style={{ padding: '24px' }}>
145
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
146
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
147
+ <div style={{
148
+ width: '48px',
149
+ height: '48px',
150
+ background: '#fecaca',
151
+ borderRadius: '50%',
152
+ display: 'flex',
153
+ alignItems: 'center',
154
+ justifyContent: 'center'
155
+ }}>
156
+ <span style={{ fontSize: '24px' }}>⚠️</span>
157
+ </div>
158
+ <div>
159
+ <h2 style={{ color: '#dc2626', margin: 0, fontSize: '20px', fontWeight: 600 }}>
160
+ {config.title}
161
+ </h2>
162
+ <p style={{ color: '#666', margin: 0, fontSize: '14px' }}>
163
+ {config.description}
164
+ </p>
165
+ </div>
166
+ </div>
167
+ {this.state.errorId && (
168
+ <span style={{
169
+ background: '#dc2626',
170
+ color: 'white',
171
+ padding: '4px 8px',
172
+ borderRadius: '4px',
173
+ fontSize: '12px',
174
+ fontWeight: 500
175
+ }}>
176
+ ID: {this.state.errorId.slice(-8)}
177
+ </span>
178
+ )}
179
+ </div>
180
+ </div>
181
+
182
+ <div style={{ padding: '0 24px 24px', display: 'flex', flexDirection: 'column', gap: '16px' }}>
183
+ {this.state.error && (
184
+ <ErrorDetails
185
+ error={this.state.error}
186
+ errorInfo={this.state.errorInfo}
187
+ showDetails={showDetails}
188
+ />
189
+ )}
190
+
191
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
192
+ <button
193
+ onClick={this.handleReset}
194
+ style={{
195
+ padding: '8px 16px',
196
+ background: 'white',
197
+ border: '1px solid #d1d5db',
198
+ borderRadius: '6px',
199
+ fontSize: '14px',
200
+ fontWeight: 500,
201
+ cursor: 'pointer',
202
+ display: 'flex',
203
+ alignItems: 'center',
204
+ gap: '8px'
205
+ }}
206
+ >
207
+ 🔄 Try Again
208
+ </button>
209
+ <button
210
+ onClick={() => window.location.reload()}
211
+ style={{
212
+ padding: '8px 16px',
213
+ background: '#3b82f6',
214
+ color: 'white',
215
+ border: 'none',
216
+ borderRadius: '6px',
217
+ fontSize: '14px',
218
+ fontWeight: 500,
219
+ cursor: 'pointer',
220
+ display: 'flex',
221
+ alignItems: 'center',
222
+ gap: '8px'
223
+ }}
224
+ >
225
+ 🔄 Refresh Page
226
+ </button>
227
+ {config.showHomeButton && (
228
+ <button
229
+ onClick={this.handleGoHome}
230
+ style={{
231
+ padding: '8px 16px',
232
+ background: '#6b7280',
233
+ color: 'white',
234
+ border: 'none',
235
+ borderRadius: '6px',
236
+ fontSize: '14px',
237
+ fontWeight: 500,
238
+ cursor: 'pointer',
239
+ display: 'flex',
240
+ alignItems: 'center',
241
+ gap: '8px'
242
+ }}
243
+ >
244
+ 🏠 Go Home
245
+ </button>
246
+ )}
247
+ </div>
248
+ </div>
249
+ </div>
250
+ </div>
251
+ );
252
+ }
253
+
254
+ return this.props.children;
255
+ }
256
+ }
257
+
258
+ export default ErrorBoundary;