react-essentials-functions 1.0.4 → 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 +514 -3
- package/build/components/ConditionnalWrapper.d.ts +20 -3
- package/build/components/ConditionnalWrapper.js +19 -1
- package/build/hooks/index.d.ts +6 -0
- package/build/hooks/index.js +6 -0
- package/build/hooks/useClickOutside.d.ts +23 -0
- package/build/hooks/useClickOutside.js +42 -0
- package/build/hooks/useDebounce.d.ts +21 -0
- package/build/hooks/useDebounce.js +36 -0
- package/build/hooks/useDimensions.d.ts +22 -2
- package/build/hooks/useDimensions.js +53 -17
- package/build/hooks/useLocalStorage.d.ts +22 -0
- package/build/hooks/useLocalStorage.js +61 -0
- package/build/hooks/useMediaQuery.d.ts +17 -0
- package/build/hooks/useMediaQuery.js +43 -0
- package/build/hooks/usePrevious.d.ts +20 -0
- package/build/hooks/usePrevious.js +30 -0
- package/build/hooks/useSafeFetch.d.ts +26 -1
- package/build/hooks/useSafeFetch.js +43 -5
- package/build/hooks/useSafeState.d.ts +3 -1
- package/build/hooks/useSafeState.js +1 -2
- package/build/hooks/useScript.d.ts +36 -1
- package/build/hooks/useScript.js +55 -3
- package/build/hooks/useTheme.d.ts +24 -2
- package/build/hooks/useTheme.js +71 -9
- package/build/hooks/useToggle.d.ts +22 -0
- package/build/hooks/useToggle.js +38 -0
- package/build/hooks/useWindowDimensions.d.ts +22 -3
- package/build/hooks/useWindowDimensions.js +35 -16
- package/package.json +39 -59
package/README.md
CHANGED
|
@@ -1,13 +1,524 @@
|
|
|
1
1
|
# react-essentials-functions
|
|
2
2
|
|
|
3
|
-
A collection of useful
|
|
3
|
+
A collection of zero-dependency useful hooks and components for React.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
yarn add react-essentials-functions
|
|
9
|
+
# or
|
|
10
|
+
npm install react-essentials-functions
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Table of Contents
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
- [Hooks](#hooks)
|
|
16
|
+
- [useClickOutside](#useclickoutside)
|
|
17
|
+
- [useDebounce](#usedebounce)
|
|
18
|
+
- [useDimensions](#usedimensions)
|
|
19
|
+
- [useLocalStorage](#uselocalstorage)
|
|
20
|
+
- [useMediaQuery](#usemediaquery)
|
|
21
|
+
- [usePrevious](#useprevious)
|
|
22
|
+
- [useSafeFetch](#usesafefetch)
|
|
23
|
+
- [useSafeState](#usesafestate)
|
|
24
|
+
- [useScript](#usescript)
|
|
25
|
+
- [useTheme](#usetheme)
|
|
26
|
+
- [useToggle](#usetoggle)
|
|
27
|
+
- [useWindowDimensions](#usewindowdimensions)
|
|
28
|
+
- [Components](#components)
|
|
29
|
+
- [ConditionalWrapper](#conditionalwrapper)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Hooks
|
|
34
|
+
|
|
35
|
+
### useClickOutside
|
|
36
|
+
|
|
37
|
+
Hook that detects clicks outside of a referenced element. Useful for closing dropdowns, modals, and popovers. Listens for both `mousedown` and `touchstart` events.
|
|
38
|
+
|
|
39
|
+
**Parameters:**
|
|
40
|
+
- `ref: RefObject<HTMLElement>` - React ref to the element to monitor
|
|
41
|
+
- `handler: (event: MouseEvent | TouchEvent) => void` - Callback fired when a click outside is detected
|
|
42
|
+
|
|
43
|
+
**Returns:**
|
|
44
|
+
- `void`
|
|
45
|
+
|
|
46
|
+
**Side effects:**
|
|
47
|
+
- Adds `mousedown` and `touchstart` event listeners on `document`
|
|
48
|
+
- Removes listeners on unmount
|
|
49
|
+
|
|
50
|
+
**Example:**
|
|
51
|
+
```tsx
|
|
52
|
+
import { useClickOutside } from 'react-essentials-functions';
|
|
53
|
+
import { useRef, useState } from 'react';
|
|
54
|
+
|
|
55
|
+
function Dropdown() {
|
|
56
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
57
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
58
|
+
|
|
59
|
+
useClickOutside(dropdownRef, () => setIsOpen(false));
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div ref={dropdownRef}>
|
|
63
|
+
<button onClick={() => setIsOpen(true)}>Open</button>
|
|
64
|
+
{isOpen && <ul><li>Option 1</li><li>Option 2</li></ul>}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### useDebounce
|
|
73
|
+
|
|
74
|
+
Hook that debounces a value by a given delay. The debounced value will only update after the specified delay has passed since the last change. Useful for search inputs, API calls, and any rapid-fire updates.
|
|
75
|
+
|
|
76
|
+
**Type Parameters:**
|
|
77
|
+
- `T` - The type of the value to debounce
|
|
78
|
+
|
|
79
|
+
**Parameters:**
|
|
80
|
+
- `value: T` - The value to debounce
|
|
81
|
+
- `delay: number` - The debounce delay in milliseconds
|
|
82
|
+
|
|
83
|
+
**Returns:**
|
|
84
|
+
- `T` - The debounced value
|
|
85
|
+
|
|
86
|
+
**Example:**
|
|
87
|
+
```tsx
|
|
88
|
+
import { useDebounce } from 'react-essentials-functions';
|
|
89
|
+
import { useState, useEffect } from 'react';
|
|
90
|
+
|
|
91
|
+
function SearchComponent() {
|
|
92
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
93
|
+
const debouncedSearch = useDebounce(searchTerm, 300);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (debouncedSearch) {
|
|
97
|
+
// This only fires 300ms after the user stops typing
|
|
98
|
+
fetchResults(debouncedSearch);
|
|
99
|
+
}
|
|
100
|
+
}, [debouncedSearch]);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<input
|
|
104
|
+
value={searchTerm}
|
|
105
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
106
|
+
placeholder="Search..."
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### useDimensions
|
|
115
|
+
|
|
116
|
+
Hook to get the dimensions of a DOM element. Uses `ResizeObserver` for optimal performance with a fallback to window events for older browsers.
|
|
117
|
+
|
|
118
|
+
**Parameters:**
|
|
119
|
+
- `targetRef: RefObject<HTMLElement>` - React ref to the element to measure
|
|
120
|
+
|
|
121
|
+
**Returns:**
|
|
122
|
+
- `Dimensions` - Object containing `width` and `height` of the element
|
|
123
|
+
|
|
124
|
+
**Side effects:**
|
|
125
|
+
- Creates a `ResizeObserver` on the target element (or falls back to `resize`/`scroll` window listeners)
|
|
126
|
+
- Cleans up on unmount
|
|
127
|
+
|
|
128
|
+
**Example:**
|
|
129
|
+
```tsx
|
|
130
|
+
import { useDimensions } from 'react-essentials-functions';
|
|
131
|
+
import { useRef } from 'react';
|
|
132
|
+
|
|
133
|
+
function MyComponent() {
|
|
134
|
+
const targetRef = useRef<HTMLDivElement>(null);
|
|
135
|
+
const { width, height } = useDimensions(targetRef);
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div ref={targetRef}>
|
|
139
|
+
Size: {width}px x {height}px
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### useLocalStorage
|
|
148
|
+
|
|
149
|
+
Hook that syncs state with `localStorage`. Handles JSON serialization/deserialization automatically. Falls back gracefully when `localStorage` is unavailable (SSR, private browsing).
|
|
150
|
+
|
|
151
|
+
**Type Parameters:**
|
|
152
|
+
- `T` - The type of the stored value
|
|
153
|
+
|
|
154
|
+
**Parameters:**
|
|
155
|
+
- `key: string` - The localStorage key
|
|
156
|
+
- `initialValue: T` - The initial value if nothing is stored
|
|
157
|
+
|
|
158
|
+
**Returns:**
|
|
159
|
+
- `[T, (value: T | ((prev: T) => T)) => void, () => void]` - A tuple containing:
|
|
160
|
+
- The current stored value
|
|
161
|
+
- A setter function (accepts value or updater function)
|
|
162
|
+
- A remove function to clear the key from localStorage
|
|
163
|
+
|
|
164
|
+
**Side effects:**
|
|
165
|
+
- Reads from `localStorage` on initialization
|
|
166
|
+
- Writes to `localStorage` on every value change
|
|
167
|
+
- `removeValue` deletes the key from `localStorage`
|
|
168
|
+
|
|
169
|
+
**Example:**
|
|
170
|
+
```tsx
|
|
171
|
+
import { useLocalStorage } from 'react-essentials-functions';
|
|
172
|
+
|
|
173
|
+
function Settings() {
|
|
174
|
+
const [name, setName, removeName] = useLocalStorage('user-name', '');
|
|
175
|
+
const [preferences, setPreferences] = useLocalStorage('prefs', {
|
|
176
|
+
notifications: true,
|
|
177
|
+
language: 'en',
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<div>
|
|
182
|
+
<input value={name} onChange={(e) => setName(e.target.value)} />
|
|
183
|
+
<button onClick={removeName}>Clear name</button>
|
|
184
|
+
<button onClick={() => setPreferences(prev => ({ ...prev, language: 'fr' }))}>
|
|
185
|
+
Switch to French
|
|
186
|
+
</button>
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### useMediaQuery
|
|
195
|
+
|
|
196
|
+
Hook that tracks whether a CSS media query matches. Listens for changes and updates automatically. Useful for responsive design, detecting dark mode preference, reduced motion, etc.
|
|
197
|
+
|
|
198
|
+
**Parameters:**
|
|
199
|
+
- `query: string` - The CSS media query string (e.g. `'(min-width: 768px)'`)
|
|
200
|
+
|
|
201
|
+
**Returns:**
|
|
202
|
+
- `boolean` - Whether the media query currently matches
|
|
203
|
+
|
|
204
|
+
**Side effects:**
|
|
205
|
+
- Adds a `change` listener on the `MediaQueryList` object
|
|
206
|
+
- Removes listener on unmount or query change
|
|
207
|
+
|
|
208
|
+
**Example:**
|
|
209
|
+
```tsx
|
|
210
|
+
import { useMediaQuery } from 'react-essentials-functions';
|
|
211
|
+
|
|
212
|
+
function ResponsiveComponent() {
|
|
213
|
+
const isMobile = useMediaQuery('(max-width: 767px)');
|
|
214
|
+
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
|
|
215
|
+
const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<div>
|
|
219
|
+
{isMobile ? <MobileLayout /> : <DesktopLayout />}
|
|
220
|
+
{prefersDark && <span>Dark mode detected</span>}
|
|
221
|
+
</div>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
### usePrevious
|
|
229
|
+
|
|
230
|
+
Hook that returns the previous value of a variable. Useful for comparing current and previous props or state values.
|
|
231
|
+
|
|
232
|
+
**Type Parameters:**
|
|
233
|
+
- `T` - The type of the tracked value
|
|
234
|
+
|
|
235
|
+
**Parameters:**
|
|
236
|
+
- `value: T` - The value to track
|
|
237
|
+
|
|
238
|
+
**Returns:**
|
|
239
|
+
- `T | undefined` - The value from the previous render, or `undefined` on first render
|
|
240
|
+
|
|
241
|
+
**Example:**
|
|
242
|
+
```tsx
|
|
243
|
+
import { usePrevious } from 'react-essentials-functions';
|
|
244
|
+
import { useState } from 'react';
|
|
245
|
+
|
|
246
|
+
function Counter() {
|
|
247
|
+
const [count, setCount] = useState(0);
|
|
248
|
+
const previousCount = usePrevious(count);
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<div>
|
|
252
|
+
<p>Current: {count}, Previous: {previousCount ?? 'N/A'}</p>
|
|
253
|
+
<button onClick={() => setCount(count + 1)}>Increment</button>
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
### useSafeFetch
|
|
262
|
+
|
|
263
|
+
Hook that provides a fetch function which automatically aborts previous requests and cleans up on unmount using `AbortController`. Prevents race conditions when multiple requests are made in sequence.
|
|
264
|
+
|
|
265
|
+
**Parameters:**
|
|
266
|
+
- None
|
|
267
|
+
|
|
268
|
+
**Returns:**
|
|
269
|
+
- `(url: string, options?: RequestInit) => Promise<Response>` - Fetch function with automatic abort handling
|
|
270
|
+
|
|
271
|
+
**Side effects:**
|
|
272
|
+
- Aborts the previous in-flight request when a new one is made
|
|
273
|
+
- Aborts any pending request on component unmount
|
|
274
|
+
- User-provided `signal` in options is ignored (the hook manages its own)
|
|
275
|
+
|
|
276
|
+
**Example:**
|
|
277
|
+
```tsx
|
|
278
|
+
import { useSafeFetch } from 'react-essentials-functions';
|
|
279
|
+
import { useEffect } from 'react';
|
|
280
|
+
|
|
281
|
+
function DataComponent() {
|
|
282
|
+
const safeFetch = useSafeFetch();
|
|
283
|
+
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
const fetchData = async () => {
|
|
286
|
+
try {
|
|
287
|
+
const response = await safeFetch('https://api.example.com/data');
|
|
288
|
+
const data = await response.json();
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (error.name !== 'AbortError') {
|
|
291
|
+
console.error('Fetch error:', error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
fetchData();
|
|
297
|
+
}, [safeFetch]);
|
|
298
|
+
|
|
299
|
+
return <div>Loading data...</div>;
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
### useSafeState
|
|
306
|
+
|
|
307
|
+
A version of `useState` that prevents state updates after the component unmounts, preventing memory leaks and "Can't perform a React state update on an unmounted component" warnings.
|
|
308
|
+
|
|
309
|
+
**Type Parameters:**
|
|
310
|
+
- `T` - The type of the state value
|
|
311
|
+
|
|
312
|
+
**Parameters:**
|
|
313
|
+
- `initialValue: T | (() => T)` - The initial state value
|
|
314
|
+
|
|
315
|
+
**Returns:**
|
|
316
|
+
- `[T, (value: T | ((prevState: T) => T)) => void]` - A tuple containing the current state and a safe setState function
|
|
317
|
+
|
|
318
|
+
**Example:**
|
|
319
|
+
```tsx
|
|
320
|
+
import { useSafeState } from 'react-essentials-functions';
|
|
321
|
+
import { useEffect } from 'react';
|
|
322
|
+
|
|
323
|
+
function UserProfile({ userId }) {
|
|
324
|
+
const [user, setUser] = useSafeState<User | null>(null);
|
|
325
|
+
|
|
326
|
+
useEffect(() => {
|
|
327
|
+
fetchUser(userId).then((data) => {
|
|
328
|
+
// Safe even if component unmounted during fetch
|
|
329
|
+
setUser(data);
|
|
330
|
+
});
|
|
331
|
+
}, [userId]);
|
|
332
|
+
|
|
333
|
+
return user ? <div>{user.name}</div> : <div>Loading...</div>;
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
### useScript
|
|
340
|
+
|
|
341
|
+
Hook to dynamically load external scripts with status tracking and callback support.
|
|
342
|
+
|
|
343
|
+
**Parameters:**
|
|
344
|
+
- `url: string` - The URL of the script to load
|
|
345
|
+
- `options?: UseScriptOptions` - Optional configuration:
|
|
346
|
+
- `onLoad?: () => void` - Callback when script loads successfully
|
|
347
|
+
- `onError?: () => void` - Callback when script fails to load
|
|
348
|
+
- `removeOnUnmount?: boolean` - Whether to remove script on unmount (default: `true`)
|
|
349
|
+
|
|
350
|
+
**Returns:**
|
|
351
|
+
- `UseScriptStatus` - The current status: `'idle' | 'loading' | 'ready' | 'error'`
|
|
352
|
+
|
|
353
|
+
**Side effects:**
|
|
354
|
+
- Appends a `<script>` tag to `document.body`
|
|
355
|
+
- Removes the script tag on unmount (unless `removeOnUnmount: false`)
|
|
356
|
+
- Detects and reuses already-loaded scripts
|
|
357
|
+
|
|
358
|
+
**Example:**
|
|
359
|
+
```tsx
|
|
360
|
+
import { useScript } from 'react-essentials-functions';
|
|
361
|
+
|
|
362
|
+
function GoogleMapsComponent() {
|
|
363
|
+
const status = useScript('https://maps.googleapis.com/maps/api/js', {
|
|
364
|
+
onLoad: () => console.log('Google Maps loaded'),
|
|
365
|
+
onError: () => console.error('Failed to load Google Maps'),
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
if (status === 'loading') return <div>Loading map...</div>;
|
|
369
|
+
if (status === 'error') return <div>Error loading map</div>;
|
|
370
|
+
if (status === 'idle') return <div>Initializing...</div>;
|
|
371
|
+
|
|
372
|
+
return <div>Map is ready!</div>;
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
### useTheme
|
|
379
|
+
|
|
380
|
+
Hook to manage theme (light/dark) with `localStorage` persistence and SSR support. Automatically detects system color scheme preference via `prefers-color-scheme` when no theme has been previously stored.
|
|
381
|
+
|
|
382
|
+
**Returns:**
|
|
383
|
+
- `[ThemeMode, () => void, boolean]` - A tuple containing:
|
|
384
|
+
- Current theme mode (`'light' | 'dark'`)
|
|
385
|
+
- Function to toggle between themes
|
|
386
|
+
- Boolean indicating if component is mounted (useful for SSR)
|
|
387
|
+
|
|
388
|
+
**Side effects:**
|
|
389
|
+
- Reads/writes to `localStorage` with key `'theme'`
|
|
390
|
+
- Detects system `prefers-color-scheme` preference on first load
|
|
391
|
+
|
|
392
|
+
**Example:**
|
|
393
|
+
```tsx
|
|
394
|
+
import { useTheme } from 'react-essentials-functions';
|
|
395
|
+
|
|
396
|
+
function ThemeToggle() {
|
|
397
|
+
const [theme, toggleTheme, mounted] = useTheme();
|
|
398
|
+
|
|
399
|
+
// Avoid hydration mismatch
|
|
400
|
+
if (!mounted) return null;
|
|
401
|
+
|
|
402
|
+
return (
|
|
403
|
+
<button onClick={toggleTheme}>
|
|
404
|
+
Switch to {theme === 'light' ? 'dark' : 'light'} mode
|
|
405
|
+
</button>
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
### useToggle
|
|
413
|
+
|
|
414
|
+
Hook for managing a boolean toggle state. Provides a simple API for toggling, setting true, or setting false. Useful for modals, dropdowns, accordions, etc.
|
|
415
|
+
|
|
416
|
+
**Parameters:**
|
|
417
|
+
- `initialValue?: boolean` - The initial boolean value (default: `false`)
|
|
418
|
+
|
|
419
|
+
**Returns:**
|
|
420
|
+
- `[boolean, () => void, () => void, () => void]` - A tuple containing:
|
|
421
|
+
- The current boolean value
|
|
422
|
+
- `toggle` - Flips the value
|
|
423
|
+
- `setTrue` - Sets to `true`
|
|
424
|
+
- `setFalse` - Sets to `false`
|
|
425
|
+
|
|
426
|
+
**Example:**
|
|
427
|
+
```tsx
|
|
428
|
+
import { useToggle } from 'react-essentials-functions';
|
|
429
|
+
|
|
430
|
+
function Modal() {
|
|
431
|
+
const [isOpen, toggleOpen, open, close] = useToggle(false);
|
|
432
|
+
|
|
433
|
+
return (
|
|
434
|
+
<div>
|
|
435
|
+
<button onClick={open}>Open Modal</button>
|
|
436
|
+
{isOpen && (
|
|
437
|
+
<div className="modal">
|
|
438
|
+
<p>Modal content</p>
|
|
439
|
+
<button onClick={close}>Close</button>
|
|
440
|
+
</div>
|
|
441
|
+
)}
|
|
442
|
+
</div>
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
### useWindowDimensions
|
|
450
|
+
|
|
451
|
+
Hook to get the current window dimensions with automatic updates on resize. SSR-safe (returns `0` for both dimensions when `window` is unavailable).
|
|
452
|
+
|
|
453
|
+
**Returns:**
|
|
454
|
+
- `WindowDimensions` - Object containing `width` and `height` of the window
|
|
455
|
+
|
|
456
|
+
**Side effects:**
|
|
457
|
+
- Adds a `resize` event listener on `window`
|
|
458
|
+
- Removes listener on unmount
|
|
459
|
+
|
|
460
|
+
**Example:**
|
|
461
|
+
```tsx
|
|
462
|
+
import { useWindowDimensions } from 'react-essentials-functions';
|
|
463
|
+
|
|
464
|
+
function ResponsiveComponent() {
|
|
465
|
+
const { width, height } = useWindowDimensions();
|
|
466
|
+
|
|
467
|
+
return (
|
|
468
|
+
<div>
|
|
469
|
+
Window size: {width}px x {height}px
|
|
470
|
+
{width < 768 ? <MobileLayout /> : <DesktopLayout />}
|
|
471
|
+
</div>
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Components
|
|
479
|
+
|
|
480
|
+
### ConditionalWrapper
|
|
481
|
+
|
|
482
|
+
Component that conditionally wraps its children with a wrapper component based on a condition. When the condition is `false`, children are rendered unwrapped inside a fragment.
|
|
483
|
+
|
|
484
|
+
**Props:**
|
|
485
|
+
- `condition: boolean` - Whether to wrap the children
|
|
486
|
+
- `wrapper: (children: React.ReactNode) => JSX.Element` - Function that returns the wrapper element
|
|
487
|
+
- `children: React.ReactNode` - Children to wrap
|
|
488
|
+
|
|
489
|
+
**Example:**
|
|
490
|
+
```tsx
|
|
491
|
+
import { ConditionalWrapper } from 'react-essentials-functions';
|
|
492
|
+
|
|
493
|
+
function LinkWrapper({ link, children }) {
|
|
494
|
+
return (
|
|
495
|
+
<ConditionalWrapper
|
|
496
|
+
condition={!!link}
|
|
497
|
+
wrapper={(c) => <a href={link}>{c}</a>}
|
|
498
|
+
>
|
|
499
|
+
<button>{children}</button>
|
|
500
|
+
</ConditionalWrapper>
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## TypeScript Support
|
|
508
|
+
|
|
509
|
+
This library is written in TypeScript and includes full type definitions. All types are exported for your convenience:
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
import type {
|
|
513
|
+
Dimensions,
|
|
514
|
+
WindowDimensions,
|
|
515
|
+
ThemeMode,
|
|
516
|
+
UseScriptStatus,
|
|
517
|
+
UseScriptOptions,
|
|
518
|
+
ConditionalWrapperProps,
|
|
519
|
+
} from 'react-essentials-functions';
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## License
|
|
523
|
+
|
|
524
|
+
MIT
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
export type
|
|
2
|
+
export type ConditionalWrapperProps = {
|
|
3
3
|
condition: boolean;
|
|
4
|
-
wrapper: (children: React.ReactNode) =>
|
|
4
|
+
wrapper: (children: React.ReactNode) => JSX.Element;
|
|
5
5
|
children: React.ReactNode;
|
|
6
6
|
};
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Component that conditionally wraps its children with a wrapper component.
|
|
9
|
+
*
|
|
10
|
+
* @param condition - Whether to wrap the children
|
|
11
|
+
* @param wrapper - Function that returns the wrapper element
|
|
12
|
+
* @param children - Children to wrap
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <ConditionalWrapper
|
|
17
|
+
* condition={!!link}
|
|
18
|
+
* wrapper={(children) => <a href={link}>{children}</a>}
|
|
19
|
+
* >
|
|
20
|
+
* <button>Click me</button>
|
|
21
|
+
* </ConditionalWrapper>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare const ConditionalWrapper: ({ condition, wrapper, children, }: ConditionalWrapperProps) => JSX.Element;
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConditionalWrapper = void 0;
|
|
4
|
-
const
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
/**
|
|
6
|
+
* Component that conditionally wraps its children with a wrapper component.
|
|
7
|
+
*
|
|
8
|
+
* @param condition - Whether to wrap the children
|
|
9
|
+
* @param wrapper - Function that returns the wrapper element
|
|
10
|
+
* @param children - Children to wrap
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <ConditionalWrapper
|
|
15
|
+
* condition={!!link}
|
|
16
|
+
* wrapper={(children) => <a href={link}>{children}</a>}
|
|
17
|
+
* >
|
|
18
|
+
* <button>Click me</button>
|
|
19
|
+
* </ConditionalWrapper>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
const ConditionalWrapper = ({ condition, wrapper, children, }) => condition ? wrapper(children) : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
|
|
5
23
|
exports.ConditionalWrapper = ConditionalWrapper;
|
package/build/hooks/index.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
export * from './useClickOutside';
|
|
2
|
+
export * from './useDebounce';
|
|
1
3
|
export * from './useDimensions';
|
|
4
|
+
export * from './useLocalStorage';
|
|
5
|
+
export * from './useMediaQuery';
|
|
6
|
+
export * from './usePrevious';
|
|
2
7
|
export * from './useSafeFetch';
|
|
3
8
|
export * from './useSafeState';
|
|
4
9
|
export * from './useScript';
|
|
5
10
|
export * from './useTheme';
|
|
11
|
+
export * from './useToggle';
|
|
6
12
|
export * from './useWindowDimensions';
|
package/build/hooks/index.js
CHANGED
|
@@ -14,9 +14,15 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./useClickOutside"), exports);
|
|
18
|
+
__exportStar(require("./useDebounce"), exports);
|
|
17
19
|
__exportStar(require("./useDimensions"), exports);
|
|
20
|
+
__exportStar(require("./useLocalStorage"), exports);
|
|
21
|
+
__exportStar(require("./useMediaQuery"), exports);
|
|
22
|
+
__exportStar(require("./usePrevious"), exports);
|
|
18
23
|
__exportStar(require("./useSafeFetch"), exports);
|
|
19
24
|
__exportStar(require("./useSafeState"), exports);
|
|
20
25
|
__exportStar(require("./useScript"), exports);
|
|
21
26
|
__exportStar(require("./useTheme"), exports);
|
|
27
|
+
__exportStar(require("./useToggle"), exports);
|
|
22
28
|
__exportStar(require("./useWindowDimensions"), exports);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Hook that detects clicks outside of a referenced element.
|
|
4
|
+
* Useful for closing dropdowns, modals, and popovers.
|
|
5
|
+
*
|
|
6
|
+
* @param ref - React ref to the element to monitor
|
|
7
|
+
* @param handler - Callback fired when a click outside is detected
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* const dropdownRef = useRef<HTMLDivElement>(null);
|
|
12
|
+
* const [isOpen, setIsOpen] = useState(false);
|
|
13
|
+
*
|
|
14
|
+
* useClickOutside(dropdownRef, () => setIsOpen(false));
|
|
15
|
+
*
|
|
16
|
+
* return (
|
|
17
|
+
* <div ref={dropdownRef}>
|
|
18
|
+
* {isOpen && <DropdownMenu />}
|
|
19
|
+
* </div>
|
|
20
|
+
* );
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function useClickOutside(ref: RefObject<HTMLElement>, handler: (event: MouseEvent | TouchEvent) => void): void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useClickOutside = useClickOutside;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
/**
|
|
6
|
+
* Hook that detects clicks outside of a referenced element.
|
|
7
|
+
* Useful for closing dropdowns, modals, and popovers.
|
|
8
|
+
*
|
|
9
|
+
* @param ref - React ref to the element to monitor
|
|
10
|
+
* @param handler - Callback fired when a click outside is detected
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const dropdownRef = useRef<HTMLDivElement>(null);
|
|
15
|
+
* const [isOpen, setIsOpen] = useState(false);
|
|
16
|
+
*
|
|
17
|
+
* useClickOutside(dropdownRef, () => setIsOpen(false));
|
|
18
|
+
*
|
|
19
|
+
* return (
|
|
20
|
+
* <div ref={dropdownRef}>
|
|
21
|
+
* {isOpen && <DropdownMenu />}
|
|
22
|
+
* </div>
|
|
23
|
+
* );
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function useClickOutside(ref, handler) {
|
|
27
|
+
(0, react_1.useEffect)(() => {
|
|
28
|
+
const listener = (event) => {
|
|
29
|
+
const element = ref.current;
|
|
30
|
+
if (!element || element.contains(event.target)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
handler(event);
|
|
34
|
+
};
|
|
35
|
+
document.addEventListener('mousedown', listener);
|
|
36
|
+
document.addEventListener('touchstart', listener);
|
|
37
|
+
return () => {
|
|
38
|
+
document.removeEventListener('mousedown', listener);
|
|
39
|
+
document.removeEventListener('touchstart', listener);
|
|
40
|
+
};
|
|
41
|
+
}, [ref, handler]);
|
|
42
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook that debounces a value by a given delay.
|
|
3
|
+
* The debounced value will only update after the specified delay has passed
|
|
4
|
+
* since the last change.
|
|
5
|
+
*
|
|
6
|
+
* @param value - The value to debounce
|
|
7
|
+
* @param delay - The debounce delay in milliseconds
|
|
8
|
+
* @returns The debounced value
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const [searchTerm, setSearchTerm] = useState('');
|
|
13
|
+
* const debouncedSearch = useDebounce(searchTerm, 300);
|
|
14
|
+
*
|
|
15
|
+
* useEffect(() => {
|
|
16
|
+
* // This will only fire 300ms after the user stops typing
|
|
17
|
+
* fetchResults(debouncedSearch);
|
|
18
|
+
* }, [debouncedSearch]);
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function useDebounce<T>(value: T, delay: number): T;
|