@websolutespa/bom-mixer-hooks 1.8.10 → 1.9.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/CHANGELOG.md +17 -0
- package/README.md +430 -0
- package/dist/index.d.ts +452 -11
- package/dist/index.js +365 -93
- package/dist/index.mjs +309 -44
- package/package.json +4 -1
- package/src/index.ts +3 -0
- package/src/useLayout/useLayout.default.ts +1 -93
- package/src/useMetaData/useMetaData.tsx +181 -0
- package/src/usePage/usePage.tsx +3 -3
- package/src/usePagination/pagination.service.ts +58 -12
- package/src/usePagination/usePagination.test.ts +22 -7
- package/src/usePagination/usePagination.ts +79 -25
- package/src/usePaginationLegacy/pagination-legacy.service.ts +32 -0
- package/src/usePaginationLegacy/usePaginationLegacy.test.ts +64 -0
- package/src/usePaginationLegacy/usePaginationLegacy.ts +47 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @websolutespa/bom-mixer-hooks
|
|
2
2
|
|
|
3
|
+
## 1.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Modified: usePagination
|
|
8
|
+
- Added: useMetaData
|
|
9
|
+
|
|
10
|
+
## 1.8.11
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Modified: usePage typings
|
|
15
|
+
- Updated dependencies
|
|
16
|
+
- Updated dependencies
|
|
17
|
+
- @websolutespa/bom-mixer-store@1.8.11
|
|
18
|
+
- @websolutespa/bom-mixer-models@1.8.18
|
|
19
|
+
|
|
3
20
|
## 1.8.10
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -9,3 +9,433 @@ Mixer Hooks module of the [BOM Repository](https://github.com/websolutespa/bom)
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
##### *this library is for internal usage and not production ready*
|
|
12
|
+
|
|
13
|
+
# @websolutespa/bom-mixer-hooks Documentation
|
|
14
|
+
|
|
15
|
+
A collection of React hooks for building modern web applications.
|
|
16
|
+
|
|
17
|
+
## Table of Contents
|
|
18
|
+
|
|
19
|
+
- [Installation](#installation)
|
|
20
|
+
- [API and Networking](#api-and-networking)
|
|
21
|
+
- [UI and Layout](#ui-and-layout)
|
|
22
|
+
- [Form and Input](#form-and-input)
|
|
23
|
+
- [Internationalization](#internationalization)
|
|
24
|
+
- [Scroll and Viewport](#scroll-and-viewport)
|
|
25
|
+
- [State Management](#state-management)
|
|
26
|
+
- [Performance](#performance)
|
|
27
|
+
- [Event Handling](#event-handling)
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @websolutespa/bom-mixer-hooks
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## API and Networking
|
|
36
|
+
|
|
37
|
+
### useApi
|
|
38
|
+
|
|
39
|
+
Hook for making RESTful API calls.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { useApi } from '@websolutespa/bom-mixer-hooks';
|
|
43
|
+
|
|
44
|
+
function MyComponent() {
|
|
45
|
+
const api = useApi();
|
|
46
|
+
|
|
47
|
+
const fetchData = async () => {
|
|
48
|
+
try {
|
|
49
|
+
// GET request
|
|
50
|
+
const data = await api.get('/endpoint');
|
|
51
|
+
|
|
52
|
+
// POST request with payload
|
|
53
|
+
const response = await api.post('/endpoint', {
|
|
54
|
+
name: 'John',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// PUT request
|
|
58
|
+
await api.put('/endpoint/1', {
|
|
59
|
+
updated: true,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// DELETE request
|
|
63
|
+
await api.delete('/endpoint/1');
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('API error:', error);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### useCollectionApi
|
|
72
|
+
|
|
73
|
+
Specialized version of useApi for data collections.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { useCollectionApi, useCollectionApiGet } from '@websolutespa/bom-mixer-hooks';
|
|
77
|
+
|
|
78
|
+
function MyCollection() {
|
|
79
|
+
const [data, loading, error] = useCollectionApiGet('/collection', {
|
|
80
|
+
where: { status: 'active' }
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (loading) return <Loading />;
|
|
84
|
+
if (error) return <Error message={error.message} />;
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div>
|
|
88
|
+
{data.map(item => (
|
|
89
|
+
<Item key={item.id} {...item} />
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## UI and Layout
|
|
97
|
+
|
|
98
|
+
### useLayout
|
|
99
|
+
|
|
100
|
+
Manages the global application layout.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { useLayout } from '@websolutespa/bom-mixer-hooks';
|
|
104
|
+
|
|
105
|
+
function Header() {
|
|
106
|
+
const layout = useLayout();
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<header>
|
|
110
|
+
<nav>
|
|
111
|
+
{layout.menu.main.items.map(item => (
|
|
112
|
+
<MenuItem key={item.id} {...item} />
|
|
113
|
+
))}
|
|
114
|
+
</nav>
|
|
115
|
+
</header>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### useDrawer
|
|
121
|
+
|
|
122
|
+
Manages drawer/sidebar state.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { useDrawer } from '@websolutespa/bom-mixer-hooks';
|
|
126
|
+
|
|
127
|
+
function Sidebar() {
|
|
128
|
+
const [drawer, openDrawer, closeDrawer] = useDrawer();
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<>
|
|
132
|
+
<button onClick={() => openDrawer('main')}>Open Menu</button>
|
|
133
|
+
|
|
134
|
+
<div className={`drawer ${drawer === 'main' ? 'open' : ''}`}>
|
|
135
|
+
<button onClick={closeDrawer}>Close</button>
|
|
136
|
+
{/* Drawer content */}
|
|
137
|
+
</div>
|
|
138
|
+
</>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### useBreakpoint
|
|
144
|
+
|
|
145
|
+
Handles responsive breakpoints.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { useBreakpoint } from '@websolutespa/bom-mixer-hooks';
|
|
149
|
+
|
|
150
|
+
function ResponsiveComponent() {
|
|
151
|
+
const breakpoint = useBreakpoint();
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<div>
|
|
155
|
+
{breakpoint.key === 'xs' && <MobileView />}
|
|
156
|
+
{breakpoint.key === 'md' && <TabletView />}
|
|
157
|
+
{breakpoint.key === 'lg' && <DesktopView />}
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Form and Input
|
|
164
|
+
|
|
165
|
+
### useAutosizeTextArea
|
|
166
|
+
|
|
167
|
+
Automatically adjusts textarea height.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { useAutosizeTextArea } from '@websolutespa/bom-mixer-hooks';
|
|
171
|
+
|
|
172
|
+
function AutoResizingTextarea() {
|
|
173
|
+
const [value, setValue] = useState('');
|
|
174
|
+
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
|
175
|
+
|
|
176
|
+
useAutosizeTextArea(textAreaRef.current, value);
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<textarea
|
|
180
|
+
ref={textAreaRef}
|
|
181
|
+
value={value}
|
|
182
|
+
onChange={(e) => setValue(e.target.value)}
|
|
183
|
+
/>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### useFocusTrap
|
|
189
|
+
|
|
190
|
+
Keeps focus within an element (useful for modals).
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { useFocusTrap } from '@websolutespa/bom-mixer-hooks';
|
|
194
|
+
|
|
195
|
+
function Modal() {
|
|
196
|
+
const modalRef = useRef(null);
|
|
197
|
+
const [isTrapped, setIsTrapped] = useFocusTrap(modalRef);
|
|
198
|
+
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
setIsTrapped(true);
|
|
201
|
+
return () => setIsTrapped(false);
|
|
202
|
+
}, []);
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<div ref={modalRef}>
|
|
206
|
+
<input type="text" />
|
|
207
|
+
<button>Action</button>
|
|
208
|
+
<button>Close</button>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Internationalization
|
|
215
|
+
|
|
216
|
+
### useLabel
|
|
217
|
+
|
|
218
|
+
Manages translations.
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { useLabel } from '@websolutespa/bom-mixer-hooks';
|
|
222
|
+
|
|
223
|
+
function Translated() {
|
|
224
|
+
const label = useLabel();
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<div>
|
|
228
|
+
<h1>{label('welcome.title')}</h1>
|
|
229
|
+
<p>{label('welcome.description', { name: 'John' })}</p>
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### useCurrency
|
|
236
|
+
|
|
237
|
+
Formats monetary values.
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { useCurrency } from '@websolutespa/bom-mixer-hooks';
|
|
241
|
+
|
|
242
|
+
function PriceDisplay() {
|
|
243
|
+
const formatCurrency = useCurrency('EUR', 'en-US');
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<div>
|
|
247
|
+
<p>Price: {formatCurrency(29.99)}</p> {/* Outputs: €29.99 */}
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Scroll and Viewport
|
|
254
|
+
|
|
255
|
+
### useIntersectionObserver
|
|
256
|
+
|
|
257
|
+
Observes when elements enter the viewport.
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
import { useIntersectionObserver } from '@websolutespa/bom-mixer-hooks';
|
|
261
|
+
|
|
262
|
+
function LazyImage() {
|
|
263
|
+
const ref = useRef(null);
|
|
264
|
+
const isVisible = useIntersectionObserver(ref, {
|
|
265
|
+
threshold: 0.1
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<div ref={ref}>
|
|
270
|
+
{isVisible && <img src="heavy-image.jpg" alt="Lazy loaded" />}
|
|
271
|
+
</div>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### useScrollTo
|
|
277
|
+
|
|
278
|
+
Manages smooth scrolling to elements.
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { useScrollTo } from '@websolutespa/bom-mixer-hooks';
|
|
282
|
+
|
|
283
|
+
function ScrollableContent() {
|
|
284
|
+
const scrollTo = useScrollTo(80); // 80px offset
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<nav>
|
|
288
|
+
<a href="#section1" onClick={scrollTo}>Go to Section 1</a>
|
|
289
|
+
<div id="section1">
|
|
290
|
+
{/* Content */}
|
|
291
|
+
</div>
|
|
292
|
+
</nav>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## State Management
|
|
298
|
+
|
|
299
|
+
### useCurrentState
|
|
300
|
+
|
|
301
|
+
Manages state with current reference.
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { useCurrentState } from '@websolutespa/bom-mixer-hooks';
|
|
305
|
+
|
|
306
|
+
function StateManager() {
|
|
307
|
+
const [value, setValue, valueRef] = useCurrentState(0);
|
|
308
|
+
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
// valueRef.current is always up to date
|
|
311
|
+
const interval = setInterval(() => {
|
|
312
|
+
console.log('Current value:', valueRef.current);
|
|
313
|
+
}, 1000);
|
|
314
|
+
|
|
315
|
+
return () => clearInterval(interval);
|
|
316
|
+
}, []);
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<button onClick={() => setValue(v => v + 1)}>
|
|
320
|
+
Count: {value}
|
|
321
|
+
</button>
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Performance
|
|
327
|
+
|
|
328
|
+
### useDebounce
|
|
329
|
+
|
|
330
|
+
Delays function execution.
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import { useDebounce } from '@websolutespa/bom-mixer-hooks';
|
|
334
|
+
|
|
335
|
+
function SearchInput() {
|
|
336
|
+
const [value, setValue] = useState('');
|
|
337
|
+
const debouncedValue = useDebounce(value, 500);
|
|
338
|
+
|
|
339
|
+
useEffect(() => {
|
|
340
|
+
// API call only after 500ms from last input
|
|
341
|
+
searchAPI(debouncedValue);
|
|
342
|
+
}, [debouncedValue]);
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<input
|
|
346
|
+
type="text"
|
|
347
|
+
value={value}
|
|
348
|
+
onChange={(e) => setValue(e.target.value)}
|
|
349
|
+
/>
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Event Handling
|
|
355
|
+
|
|
356
|
+
### useEventListener
|
|
357
|
+
|
|
358
|
+
Safely manages event listeners.
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
import { useEventListener } from '@websolutespa/bom-mixer-hooks';
|
|
362
|
+
|
|
363
|
+
function EventHandler() {
|
|
364
|
+
const handleKeyPress = (e: KeyboardEvent) => {
|
|
365
|
+
if (e.key === 'Escape') {
|
|
366
|
+
// Handle escape key
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
useEventListener('keydown', handleKeyPress);
|
|
371
|
+
|
|
372
|
+
return <div>Press ESC to trigger action</div>;
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Best Practices
|
|
377
|
+
|
|
378
|
+
1. **Hook Composition**
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
function ComplexComponent() {
|
|
382
|
+
const layout = useLayout();
|
|
383
|
+
const breakpoint = useBreakpoint();
|
|
384
|
+
const [drawer] = useDrawer();
|
|
385
|
+
|
|
386
|
+
// Combine hooks for advanced functionality
|
|
387
|
+
const isMobileMenuOpen = breakpoint.key === 'xs' && drawer === 'menu';
|
|
388
|
+
|
|
389
|
+
return (
|
|
390
|
+
<div className={isMobileMenuOpen ? 'mobile-menu' : ''}>
|
|
391
|
+
{/* Component content */}
|
|
392
|
+
</div>
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
2. **Error Handling**
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
function DataFetcher() {
|
|
401
|
+
const [data, loading, error] = useCollectionApiGet('/api/data');
|
|
402
|
+
|
|
403
|
+
if (error) {
|
|
404
|
+
return <ErrorBoundary error={error} />;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (loading) {
|
|
408
|
+
return <LoadingSpinner />;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return <DataDisplay data={data} />;
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
3. **Performance Optimization**
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
function OptimizedComponent() {
|
|
419
|
+
const debouncedSearch = useDebounceCallback((term: string) => {
|
|
420
|
+
searchAPI(term);
|
|
421
|
+
}, 300);
|
|
422
|
+
|
|
423
|
+
const intersectionRef = useRef(null);
|
|
424
|
+
const isVisible = useIntersectionObserver(intersectionRef);
|
|
425
|
+
|
|
426
|
+
// Load heavy content only when visible
|
|
427
|
+
return (
|
|
428
|
+
<div ref={intersectionRef}>
|
|
429
|
+
{isVisible && <HeavyContent />}
|
|
430
|
+
</div>
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Contributing
|
|
436
|
+
|
|
437
|
+
Please read our [Contributing Guide](CONTRIBUTING.md) before submitting a Pull Request to the project.
|
|
438
|
+
|
|
439
|
+
## License
|
|
440
|
+
|
|
441
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|