@delightui/components 0.1.110 → 0.1.111

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 (33) hide show
  1. package/dist/cjs/components/molecules/Search/Search.d.ts +18 -0
  2. package/dist/cjs/components/molecules/Search/Search.presenter.d.ts +320 -0
  3. package/dist/cjs/components/molecules/Search/Search.types.d.ts +53 -0
  4. package/dist/cjs/components/molecules/Search/index.d.ts +2 -0
  5. package/dist/cjs/components/molecules/index.d.ts +2 -0
  6. package/dist/cjs/components/utils/RenderStateView/RenderStateView.types.d.ts +4 -0
  7. package/dist/cjs/components/utils/RenderStateView/usePresenter.d.ts +1 -0
  8. package/dist/cjs/components/utils/index.d.ts +2 -0
  9. package/dist/cjs/components/utils/useDebounce/index.d.ts +1 -0
  10. package/dist/cjs/components/utils/useDebounce/useDebounce.d.ts +10 -0
  11. package/dist/cjs/library.css +66 -0
  12. package/dist/cjs/library.js +3 -3
  13. package/dist/cjs/library.js.map +1 -1
  14. package/dist/esm/components/molecules/Search/Search.d.ts +18 -0
  15. package/dist/esm/components/molecules/Search/Search.presenter.d.ts +320 -0
  16. package/dist/esm/components/molecules/Search/Search.types.d.ts +53 -0
  17. package/dist/esm/components/molecules/Search/index.d.ts +2 -0
  18. package/dist/esm/components/molecules/index.d.ts +2 -0
  19. package/dist/esm/components/utils/RenderStateView/RenderStateView.types.d.ts +4 -0
  20. package/dist/esm/components/utils/RenderStateView/usePresenter.d.ts +1 -0
  21. package/dist/esm/components/utils/index.d.ts +2 -0
  22. package/dist/esm/components/utils/useDebounce/index.d.ts +1 -0
  23. package/dist/esm/components/utils/useDebounce/useDebounce.d.ts +10 -0
  24. package/dist/esm/library.css +66 -0
  25. package/dist/esm/library.js +3 -3
  26. package/dist/esm/library.js.map +1 -1
  27. package/dist/index.d.ts +86 -1
  28. package/docs/README.md +6 -0
  29. package/docs/components/atoms/Input.md +0 -63
  30. package/docs/components/molecules/Search.md +710 -0
  31. package/docs/components/utils/RenderStateView.md +137 -38
  32. package/docs/components/utils/useDebounce.md +576 -0
  33. package/package.json +1 -1
@@ -0,0 +1,576 @@
1
+ # useDebounce
2
+
3
+ ## Description
4
+
5
+ A React hook that debounces callback functions, preventing them from being called too frequently. This is particularly useful for optimizing performance in scenarios like search inputs, API calls, window resize handlers, or any function that might be triggered rapidly. The hook provides both a debounced version of the callback and a cancel function to clear pending executions.
6
+
7
+ ## Aliases
8
+
9
+ - useDebounce
10
+ - Debounce Hook
11
+ - Throttled Callback Hook
12
+ - Delayed Execution Hook
13
+
14
+ ## Hook Signature
15
+
16
+ ```tsx
17
+ function useDebounce<T extends (...args: any[]) => void>(
18
+ callback: T,
19
+ delay: number
20
+ ): {
21
+ debouncedCallback: (...args: Parameters<T>) => void;
22
+ cancel: () => void;
23
+ }
24
+ ```
25
+
26
+ ## Parameters
27
+
28
+ | Parameter | Type | Required | Description |
29
+ |-----------|------|----------|-------------|
30
+ | `callback` | `T extends (...args: any[]) => void` | Yes | The function to debounce |
31
+ | `delay` | `number` | Yes | The debounce delay in milliseconds |
32
+
33
+ ## Return Value
34
+
35
+ Returns an object with:
36
+ - **`debouncedCallback`**: The debounced version of the callback function
37
+ - **`cancel`**: Function to cancel any pending execution
38
+
39
+ ## Examples
40
+
41
+ ### Basic Search Input Debouncing
42
+ ```tsx
43
+ import React, { useState } from 'react';
44
+ import { useDebounce, Input } from '@delightui/components';
45
+
46
+ function SearchExample() {
47
+ const [query, setQuery] = useState('');
48
+ const [results, setResults] = useState([]);
49
+ const [loading, setLoading] = useState(false);
50
+
51
+ const performSearch = async (searchQuery: string) => {
52
+ if (!searchQuery.trim()) return;
53
+
54
+ setLoading(true);
55
+ try {
56
+ const response = await fetch(`/api/search?q=${encodeURIComponent(searchQuery)}`);
57
+ const data = await response.json();
58
+ setResults(data.results);
59
+ } finally {
60
+ setLoading(false);
61
+ }
62
+ };
63
+
64
+ const { debouncedCallback: debouncedSearch } = useDebounce(performSearch, 300);
65
+
66
+ const handleInputChange = (value: string) => {
67
+ setQuery(value);
68
+ debouncedSearch(value);
69
+ };
70
+
71
+ return (
72
+ <div>
73
+ <Input
74
+ value={query}
75
+ onValueChange={handleInputChange}
76
+ placeholder="Search..."
77
+ />
78
+ {loading && <div>Searching...</div>}
79
+ <div>
80
+ {results.map(result => (
81
+ <div key={result.id}>{result.title}</div>
82
+ ))}
83
+ </div>
84
+ </div>
85
+ );
86
+ }
87
+ ```
88
+
89
+ ### Form Validation with Debouncing
90
+ ```tsx
91
+ function FormValidationExample() {
92
+ const [formData, setFormData] = useState({
93
+ username: '',
94
+ email: ''
95
+ });
96
+ const [errors, setErrors] = useState({});
97
+ const [validating, setValidating] = useState(false);
98
+
99
+ const validateForm = async (data: typeof formData) => {
100
+ setValidating(true);
101
+ try {
102
+ const response = await fetch('/api/validate', {
103
+ method: 'POST',
104
+ headers: { 'Content-Type': 'application/json' },
105
+ body: JSON.stringify(data)
106
+ });
107
+ const validation = await response.json();
108
+ setErrors(validation.errors || {});
109
+ } finally {
110
+ setValidating(false);
111
+ }
112
+ };
113
+
114
+ const { debouncedCallback: debouncedValidate } = useDebounce(validateForm, 500);
115
+
116
+ const updateField = (field: string, value: string) => {
117
+ const newData = { ...formData, [field]: value };
118
+ setFormData(newData);
119
+ debouncedValidate(newData);
120
+ };
121
+
122
+ return (
123
+ <Form>
124
+ <FormField
125
+ name="username"
126
+ label="Username"
127
+ invalid={!!errors.username}
128
+ message={errors.username}
129
+ >
130
+ <Input
131
+ value={formData.username}
132
+ onValueChange={(value) => updateField('username', value)}
133
+ placeholder="Enter username"
134
+ />
135
+ </FormField>
136
+
137
+ <FormField
138
+ name="email"
139
+ label="Email"
140
+ invalid={!!errors.email}
141
+ message={errors.email}
142
+ >
143
+ <Input
144
+ value={formData.email}
145
+ onValueChange={(value) => updateField('email', value)}
146
+ placeholder="Enter email"
147
+ />
148
+ </FormField>
149
+
150
+ {validating && (
151
+ <div className="validation-indicator">
152
+ <Spinner size="Small" />
153
+ <Text type="BodySmall">Validating...</Text>
154
+ </div>
155
+ )}
156
+ </Form>
157
+ );
158
+ }
159
+ ```
160
+
161
+ ### Auto-save Functionality
162
+ ```tsx
163
+ function AutoSaveExample() {
164
+ const [document, setDocument] = useState({
165
+ title: '',
166
+ content: '',
167
+ lastSaved: null
168
+ });
169
+ const [saveStatus, setSaveStatus] = useState('saved'); // 'saving', 'saved', 'error'
170
+
171
+ const saveDocument = async (docData: typeof document) => {
172
+ setSaveStatus('saving');
173
+ try {
174
+ const response = await fetch('/api/documents/save', {
175
+ method: 'POST',
176
+ headers: { 'Content-Type': 'application/json' },
177
+ body: JSON.stringify(docData)
178
+ });
179
+
180
+ if (response.ok) {
181
+ setSaveStatus('saved');
182
+ setDocument(prev => ({ ...prev, lastSaved: new Date() }));
183
+ } else {
184
+ setSaveStatus('error');
185
+ }
186
+ } catch (error) {
187
+ setSaveStatus('error');
188
+ }
189
+ };
190
+
191
+ const { debouncedCallback: debouncedSave, cancel: cancelSave } = useDebounce(
192
+ saveDocument,
193
+ 2000
194
+ );
195
+
196
+ const updateDocument = (updates: Partial<typeof document>) => {
197
+ const newDoc = { ...document, ...updates };
198
+ setDocument(newDoc);
199
+ debouncedSave(newDoc);
200
+ };
201
+
202
+ const forceSave = () => {
203
+ cancelSave(); // Cancel pending auto-save
204
+ saveDocument(document); // Save immediately
205
+ };
206
+
207
+ return (
208
+ <div className="document-editor">
209
+ <div className="editor-header">
210
+ <Input
211
+ value={document.title}
212
+ onValueChange={(title) => updateDocument({ title })}
213
+ placeholder="Document title..."
214
+ className="title-input"
215
+ />
216
+
217
+ <div className="save-status">
218
+ {saveStatus === 'saving' && (
219
+ <>
220
+ <Spinner size="Small" />
221
+ <Text type="BodySmall">Saving...</Text>
222
+ </>
223
+ )}
224
+ {saveStatus === 'saved' && (
225
+ <>
226
+ <Icon icon="Check" size="Small" />
227
+ <Text type="BodySmall">
228
+ Saved {document.lastSaved ? formatTime(document.lastSaved) : ''}
229
+ </Text>
230
+ </>
231
+ )}
232
+ {saveStatus === 'error' && (
233
+ <>
234
+ <Icon icon="Error" size="Small" />
235
+ <Text type="BodySmall">Save failed</Text>
236
+ </>
237
+ )}
238
+ </div>
239
+
240
+ <Button size="Small" onClick={forceSave}>
241
+ Save Now
242
+ </Button>
243
+ </div>
244
+
245
+ <TextArea
246
+ value={document.content}
247
+ onValueChange={(content) => updateDocument({ content })}
248
+ placeholder="Start writing..."
249
+ rows={20}
250
+ className="content-editor"
251
+ />
252
+ </div>
253
+ );
254
+ }
255
+ ```
256
+
257
+ ### Window Resize Handler
258
+ ```tsx
259
+ function ResponsiveLayoutExample() {
260
+ const [windowSize, setWindowSize] = useState({
261
+ width: window.innerWidth,
262
+ height: window.innerHeight
263
+ });
264
+ const [layout, setLayout] = useState('desktop');
265
+
266
+ const updateLayout = () => {
267
+ const { width } = windowSize;
268
+ if (width < 768) {
269
+ setLayout('mobile');
270
+ } else if (width < 1024) {
271
+ setLayout('tablet');
272
+ } else {
273
+ setLayout('desktop');
274
+ }
275
+ };
276
+
277
+ const handleResize = () => {
278
+ setWindowSize({
279
+ width: window.innerWidth,
280
+ height: window.innerHeight
281
+ });
282
+ };
283
+
284
+ const { debouncedCallback: debouncedResize } = useDebounce(handleResize, 100);
285
+
286
+ useEffect(() => {
287
+ window.addEventListener('resize', debouncedResize);
288
+ return () => window.removeEventListener('resize', debouncedResize);
289
+ }, [debouncedResize]);
290
+
291
+ useEffect(() => {
292
+ updateLayout();
293
+ }, [windowSize]);
294
+
295
+ return (
296
+ <div className={`layout layout-${layout}`}>
297
+ <div className="debug-info">
298
+ <Text type="BodySmall">
299
+ {windowSize.width} x {windowSize.height} ({layout})
300
+ </Text>
301
+ </div>
302
+
303
+ {layout === 'mobile' && <MobileLayout />}
304
+ {layout === 'tablet' && <TabletLayout />}
305
+ {layout === 'desktop' && <DesktopLayout />}
306
+ </div>
307
+ );
308
+ }
309
+ ```
310
+
311
+ ### API Rate Limiting
312
+ ```tsx
313
+ function RateLimitedAPIExample() {
314
+ const [suggestions, setSuggestions] = useState([]);
315
+ const [loading, setLoading] = useState(false);
316
+ const [requestCount, setRequestCount] = useState(0);
317
+
318
+ const fetchSuggestions = async (query: string) => {
319
+ if (!query.trim()) {
320
+ setSuggestions([]);
321
+ return;
322
+ }
323
+
324
+ setLoading(true);
325
+ setRequestCount(prev => prev + 1);
326
+
327
+ try {
328
+ const response = await fetch(`/api/suggestions?q=${encodeURIComponent(query)}`);
329
+ const data = await response.json();
330
+ setSuggestions(data.suggestions);
331
+ } catch (error) {
332
+ console.error('Failed to fetch suggestions:', error);
333
+ } finally {
334
+ setLoading(false);
335
+ }
336
+ };
337
+
338
+ // Debounce API calls to respect rate limits
339
+ const { debouncedCallback: debouncedFetch } = useDebounce(fetchSuggestions, 500);
340
+
341
+ return (
342
+ <div>
343
+ <div className="api-stats">
344
+ <Text type="BodySmall">
345
+ API Requests: {requestCount}
346
+ </Text>
347
+ </div>
348
+
349
+ <Input
350
+ onValueChange={debouncedFetch}
351
+ placeholder="Type to get suggestions..."
352
+ />
353
+
354
+ {loading && <Spinner size="Small" />}
355
+
356
+ <div className="suggestions">
357
+ {suggestions.map((suggestion, index) => (
358
+ <div key={index} className="suggestion-item">
359
+ {suggestion}
360
+ </div>
361
+ ))}
362
+ </div>
363
+ </div>
364
+ );
365
+ }
366
+ ```
367
+
368
+ ### Bulk Operations with Progress
369
+ ```tsx
370
+ function BulkOperationExample() {
371
+ const [selectedItems, setSelectedItems] = useState([]);
372
+ const [processing, setProcessing] = useState(false);
373
+ const [progress, setProgress] = useState(0);
374
+
375
+ const processItems = async (items: string[]) => {
376
+ if (items.length === 0) return;
377
+
378
+ setProcessing(true);
379
+ setProgress(0);
380
+
381
+ for (let i = 0; i < items.length; i++) {
382
+ try {
383
+ await fetch(`/api/process/${items[i]}`, { method: 'POST' });
384
+ setProgress(((i + 1) / items.length) * 100);
385
+ } catch (error) {
386
+ console.error(`Failed to process item ${items[i]}:`, error);
387
+ }
388
+ }
389
+
390
+ setProcessing(false);
391
+ setSelectedItems([]);
392
+ };
393
+
394
+ // Debounce bulk operations to prevent accidental multiple executions
395
+ const { debouncedCallback: debouncedProcess } = useDebounce(processItems, 1000);
396
+
397
+ const handleBulkAction = () => {
398
+ if (selectedItems.length > 0) {
399
+ debouncedProcess([...selectedItems]);
400
+ }
401
+ };
402
+
403
+ return (
404
+ <div>
405
+ <div className="bulk-actions">
406
+ <Text type="BodyMedium">
407
+ {selectedItems.length} items selected
408
+ </Text>
409
+ <Button
410
+ onClick={handleBulkAction}
411
+ disabled={selectedItems.length === 0 || processing}
412
+ >
413
+ Process Selected
414
+ </Button>
415
+ </div>
416
+
417
+ {processing && (
418
+ <div className="progress-indicator">
419
+ <ProgressBar value={progress} max={100} />
420
+ <Text type="BodySmall">
421
+ Processing... {Math.round(progress)}%
422
+ </Text>
423
+ </div>
424
+ )}
425
+
426
+ {/* Item list with selection */}
427
+ <div className="item-list">
428
+ {items.map(item => (
429
+ <CheckboxItem
430
+ key={item.id}
431
+ checked={selectedItems.includes(item.id)}
432
+ onCheckedChange={(checked) => {
433
+ if (checked) {
434
+ setSelectedItems(prev => [...prev, item.id]);
435
+ } else {
436
+ setSelectedItems(prev => prev.filter(id => id !== item.id));
437
+ }
438
+ }}
439
+ >
440
+ {item.name}
441
+ </CheckboxItem>
442
+ ))}
443
+ </div>
444
+ </div>
445
+ );
446
+ }
447
+ ```
448
+
449
+ ### Smart Notifications
450
+ ```tsx
451
+ function SmartNotificationExample() {
452
+ const [notifications, setNotifications] = useState([]);
453
+ const [pendingNotification, setPendingNotification] = useState(null);
454
+
455
+ const showNotification = (message: string, type: 'info' | 'success' | 'error' = 'info') => {
456
+ const notification = {
457
+ id: Date.now(),
458
+ message,
459
+ type,
460
+ timestamp: new Date()
461
+ };
462
+
463
+ setNotifications(prev => [...prev, notification]);
464
+
465
+ // Auto-remove after 5 seconds
466
+ setTimeout(() => {
467
+ setNotifications(prev => prev.filter(n => n.id !== notification.id));
468
+ }, 5000);
469
+ };
470
+
471
+ // Debounce similar notifications to prevent spam
472
+ const { debouncedCallback: debouncedNotify } = useDebounce(showNotification, 1000);
473
+
474
+ const handleAction = (actionType: string) => {
475
+ switch (actionType) {
476
+ case 'save':
477
+ debouncedNotify('Document saved successfully', 'success');
478
+ break;
479
+ case 'error':
480
+ debouncedNotify('An error occurred', 'error');
481
+ break;
482
+ case 'info':
483
+ debouncedNotify('Information updated', 'info');
484
+ break;
485
+ }
486
+ };
487
+
488
+ return (
489
+ <div>
490
+ <div className="action-buttons">
491
+ <Button onClick={() => handleAction('save')}>
492
+ Save (will be debounced)
493
+ </Button>
494
+ <Button onClick={() => handleAction('error')}>
495
+ Trigger Error
496
+ </Button>
497
+ <Button onClick={() => handleAction('info')}>
498
+ Show Info
499
+ </Button>
500
+ </div>
501
+
502
+ <div className="notifications">
503
+ {notifications.map(notification => (
504
+ <div
505
+ key={notification.id}
506
+ className={`notification notification-${notification.type}`}
507
+ >
508
+ <Text type="BodyMedium">{notification.message}</Text>
509
+ <Text type="BodySmall">
510
+ {formatTime(notification.timestamp)}
511
+ </Text>
512
+ </div>
513
+ ))}
514
+ </div>
515
+ </div>
516
+ );
517
+ }
518
+ ```
519
+
520
+ ## Performance Benefits
521
+
522
+ 1. **Reduces Function Calls**: Prevents excessive execution of expensive operations
523
+ 2. **API Rate Limiting**: Helps comply with API rate limits and reduces server load
524
+ 3. **UI Responsiveness**: Prevents UI blocking from frequent updates
525
+ 4. **Memory Efficiency**: Automatic cleanup prevents memory leaks
526
+ 5. **Bandwidth Optimization**: Reduces unnecessary network requests
527
+
528
+ ## Best Practices
529
+
530
+ 1. **Choose Appropriate Delays**:
531
+ - Search inputs: 300-500ms
532
+ - Form validation: 500-1000ms
533
+ - Auto-save: 1000-3000ms
534
+ - Resize handlers: 100-250ms
535
+
536
+ 2. **Stable Callback References**: Use `useCallback` for callback functions to prevent unnecessary re-debouncing
537
+
538
+ 3. **Cleanup on Unmount**: The hook automatically handles cleanup, but you can manually cancel if needed
539
+
540
+ 4. **Error Handling**: Always wrap debounced API calls in try-catch blocks
541
+
542
+ ## Common Patterns
543
+
544
+ ### Search Input
545
+ ```tsx
546
+ const { debouncedCallback: debouncedSearch } = useDebounce(searchFunction, 300);
547
+ ```
548
+
549
+ ### Form Validation
550
+ ```tsx
551
+ const { debouncedCallback: debouncedValidate } = useDebounce(validateForm, 500);
552
+ ```
553
+
554
+ ### Auto-save
555
+ ```tsx
556
+ const { debouncedCallback: debouncedSave, cancel: cancelSave } = useDebounce(saveDocument, 2000);
557
+ ```
558
+
559
+ ## TypeScript Support
560
+
561
+ The hook is fully typed and preserves the original function signature:
562
+
563
+ ```tsx
564
+ const myFunction = (a: string, b: number) => console.log(a, b);
565
+ const { debouncedCallback } = useDebounce(myFunction, 500);
566
+
567
+ // TypeScript knows the parameters are (a: string, b: number)
568
+ debouncedCallback('hello', 42); // ✅ Correctly typed
569
+ debouncedCallback(42, 'hello'); // ❌ TypeScript error
570
+ ```
571
+
572
+ ## Related Hooks
573
+
574
+ - **[React.useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback)** - Memoization for functions
575
+ - **[React.useEffect](https://reactjs.org/docs/hooks-reference.html#useeffect)** - Side effects management
576
+ - **[Search Component](../molecules/Search.md)** - Uses useDebounce internally
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delightui/components",
3
- "version": "0.1.110",
3
+ "version": "0.1.111",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "start": "vite",