@pushwoosh/kit-helpers 4.8.2 → 4.8.4

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.
@@ -0,0 +1,681 @@
1
+
2
+
3
+ ---
4
+ title: "@pushwoosh/kit-helpers — API Reference"
5
+ version: "2.0.0"
6
+ description: "React component library for building Pushwoosh products with TypeScript, styled-components, and comprehensive hook utilities"
7
+ ---
8
+
9
+ # @pushwoosh/kit-helpers
10
+
11
+ **Version:** 2.0.0
12
+
13
+ React component library for building Pushwoosh products with TypeScript, styled-components, and comprehensive hook utilities.
14
+
15
+ > This page is generated from `ai-documentation.json` and groups the API by Components, Hooks, Utilities, and Types. All styled-component props are prefixed with `$` to avoid DOM prop warnings.
16
+
17
+ ---
18
+
19
+ ## Components
20
+
21
+ ### GridContainer
22
+ - **Path:** `~/src/grid`
23
+ - **Description:** Base grid container component using CSS Grid for flexible layouts
24
+ - **Interface:**
25
+ ```ts
26
+ GridContainer<{
27
+ $alignContent?: 'center' | 'start' | 'end' | 'space-between' | 'space-around' | 'space-evenly' | 'stretch';
28
+ $alignItems?: 'center' | 'start' | 'end' | 'baseline' | 'stretch';
29
+ $bgColor?: string;
30
+ $border?: string | CssBoxSides<string>;
31
+ $borderRadius?: StrNum;
32
+ $boxShadow?: string;
33
+ $gap?: StrNum;
34
+ $gridAutoFlow?: 'row' | 'column' | 'dense' | 'row dense' | 'column dense';
35
+ $justifyContent?: 'center' | 'start' | 'end' | 'space-between' | 'space-around' | 'space-evenly' | 'stretch';
36
+ $justifyItems?: 'center' | 'start' | 'end' | 'baseline' | 'stretch';
37
+ $margin?: StrNum | CssBoxSides<StrNum>;
38
+ $padding?: StrNum | CssBoxSides<StrNum>;
39
+ $placeItems?: 'center' | 'start' | 'end' | 'stretch';
40
+ }>
41
+ ```
42
+
43
+ **Props**
44
+
45
+ | Name | Type | Required | Default | Description | Values |
46
+ |---|---|:---:|:---:|---|---|
47
+ | `$alignContent` | enum | | | Aligns the grid content along the cross axis | `center`, `start`, `end`, `space-between`, `space-around`, `space-evenly`, `stretch` |
48
+ | `$alignItems` | enum | | | Aligns grid items along the cross axis | `center`, `start`, `end`, `baseline`, `stretch` |
49
+ | `$bgColor` | string | | | Background color of the grid container | |
50
+ | `$border` | object | | | Border styling, can be string or object with top/right/bottom/left properties | |
51
+ | `$borderRadius` | string\|number | | | Border radius, accepts number (converted to px) or string | |
52
+ | `$boxShadow` | string | | | CSS box shadow property | |
53
+ | `$gap` | string\|number | | | Gap between grid items, accepts number (converted to px) or string | |
54
+ | `$gridAutoFlow` | enum | | | Controls how auto-placed items are inserted in the grid | `row`, `column`, `dense`, `row dense`, `column dense` |
55
+ | `$justifyContent` | enum | | | Aligns the grid content along the main axis | `center`, `start`, `end`, `space-between`, `space-around`, `space-evenly`, `stretch` |
56
+ | `$justifyItems` | enum | | | Aligns grid items along the main axis | `center`, `start`, `end`, `baseline`, `stretch` |
57
+ | `$margin` | object | | | Margin spacing, can be number/string or object with directional properties | |
58
+ | `$padding` | object | | | Padding spacing, can be number/string or object with directional properties | |
59
+ | `$placeItems` | enum | | | Shorthand for align-items and justify-items | `center`, `start`, `end`, `stretch` |
60
+
61
+ **Example**
62
+ ```tsx
63
+ <GridContainer $gap={16} $padding={20} $bgColor="#f0f0f0">
64
+ <div>Item 1</div>
65
+ <div>Item 2</div>
66
+ </GridContainer>
67
+ ```
68
+
69
+ **Notes**
70
+ - All props are prefixed with `$` to indicate they are styled-component props
71
+ - Numbers are automatically converted to `px` units
72
+ - Use `CssBoxSides` for asymmetric margin/padding/border
73
+ - Prefer specific layout components (Columns, Rows, Horizontal, Vertical) for common patterns
74
+
75
+ ---
76
+
77
+ ### Columns
78
+ - **Path:** `~/src/grid`
79
+ - **Description:** Grid container that creates equal or custom-sized columns
80
+ - **Interface:** `Columns<{ $sizes: StrNum; ...GridContainerProps }>`
81
+
82
+ **Props**
83
+
84
+ | Name | Type | Required | Default | Description |
85
+ |---|---|:---:|:---:|---|
86
+ | `$sizes` | string\|number | ✓ | | Number for equal columns (e.g., `3`) or string for custom sizing (e.g., `"1fr 2fr"`) |
87
+
88
+ **Examples**
89
+ ```tsx
90
+ <Columns $sizes={3} $gap={8}>
91
+ <div>Col 1</div>
92
+ <div>Col 2</div>
93
+ <div>Col 3</div>
94
+ </Columns>
95
+ ```
96
+ ```tsx
97
+ <Columns $sizes="1fr 2fr" $gap={8}>
98
+ <div>Narrow</div>
99
+ <div>Wide</div>
100
+ </Columns>
101
+ ```
102
+
103
+ **Notes**
104
+ - Extends GridContainer with all its props
105
+ - Number value creates equal columns using `repeat(n, 1fr)`
106
+ - String value is passed directly to `grid-template-columns`
107
+ - Ideal for responsive layouts with fixed column counts
108
+
109
+ ---
110
+
111
+ ### Rows
112
+ - **Path:** `~/src/grid`
113
+ - **Description:** Grid container that creates equal or custom-sized rows
114
+ - **Interface:** `Rows<{ $sizes: StrNum; ...GridContainerProps }>`
115
+
116
+ **Props**
117
+
118
+ | Name | Type | Required | Default | Description |
119
+ |---|---|:---:|:---:|---|
120
+ | `$sizes` | string\|number | ✓ | | Number for equal rows or string for custom sizing |
121
+
122
+ **Example**
123
+ ```tsx
124
+ <Rows $sizes={3} $gap={10}>
125
+ <div>Row 1</div>
126
+ <div>Row 2</div>
127
+ <div>Row 3</div>
128
+ </Rows>
129
+ ```
130
+
131
+ **Notes**
132
+ - Similar to Columns but for vertical layouts
133
+ - Extends GridContainer with all its props
134
+ - Use for vertical stacking with specific row heights
135
+
136
+ ---
137
+
138
+ ### Horizontal
139
+ - **Path:** `~/src/grid`
140
+ - **Description:** Grid container with automatic column flow for horizontal layouts
141
+ - **Interface:** `Horizontal<GridContainerProps>`
142
+
143
+ **Example**
144
+ ```tsx
145
+ <Horizontal $justifyContent="start" $gap={8}>
146
+ <Button>One</Button>
147
+ <Button>Two</Button>
148
+ <Button>Three</Button>
149
+ </Horizontal>
150
+ ```
151
+
152
+ **Notes**
153
+ - Pre-configured with `gridAutoFlow: 'column'`
154
+ - Perfect for button groups, inline elements, or navigation items
155
+ - Inherits all GridContainer props
156
+ - Items flow horizontally automatically
157
+
158
+ ---
159
+
160
+ ### Vertical
161
+ - **Path:** `~/src/grid`
162
+ - **Description:** Grid container with automatic row flow for vertical layouts
163
+ - **Interface:** `Vertical<GridContainerProps>`
164
+
165
+ **Example**
166
+ ```tsx
167
+ <Vertical $gap={10}>
168
+ <Header />
169
+ <Content />
170
+ <Footer />
171
+ </Vertical>
172
+ ```
173
+
174
+ **Notes**
175
+ - Pre-configured with `gridAutoFlow: 'row'`
176
+ - Default behavior for vertical stacking
177
+ - Inherits all GridContainer props
178
+ - Use when you need explicit vertical flow control
179
+
180
+ ---
181
+
182
+ ### Collapse
183
+ - **Path:** `~/src/expand`
184
+ - **Description:** Animated collapsible component with height transition
185
+ - **Interface:** `Collapse<{ expanded: boolean; timeout?: number; className?: string; internalClassName?: string; children: (() => ReactNode) | ReactNode; }>`
186
+
187
+ **Props**
188
+
189
+ | Name | Type | Required | Default | Description |
190
+ |---|---|:---:|:---:|---|
191
+ | `expanded` | boolean | ✓ | | Controls whether content is visible or collapsed |
192
+ | `timeout` | number | | `100` | Animation duration in milliseconds |
193
+ | `className` | string | | | CSS class for the outer wrapper |
194
+ | `internalClassName` | string | | | CSS class for the inner content wrapper |
195
+ | `children` | function\|ReactNode | ✓ | | Content to show/hide, can be render function for lazy rendering |
196
+
197
+ **Examples**
198
+ ```tsx
199
+ <Collapse expanded={isOpen}>
200
+ <div>Collapsible content here</div>
201
+ </Collapse>
202
+ ```
203
+ ```tsx
204
+ <Collapse expanded={isOpen}>
205
+ {() => <ExpensiveComponent />}
206
+ </Collapse>
207
+ ```
208
+
209
+ **Notes**
210
+ - Uses `react-transition-group` for animations
211
+ - Height is calculated dynamically from content
212
+ - Function children enable lazy rendering for performance
213
+ - Smooth height and opacity transitions
214
+ - Content is removed from DOM when collapsed
215
+
216
+ ---
217
+
218
+ ### ExpandTransition
219
+ - **Path:** `~/src/expand`
220
+ - **Description:** Advanced expand/collapse transition with zoom effect
221
+ - **Interface:** `ExpandTransition<{ timeout?: number; className?: string; externalClassName?: string; internalClassName?: string; }>`
222
+
223
+ **Props**
224
+
225
+ | Name | Type | Required | Default | Description |
226
+ |---|---|:---:|:---:|---|
227
+ | `timeout` | number | | `100` | Animation duration in milliseconds |
228
+ | `className` | string | | | Base CSS class for transition states |
229
+ | `externalClassName` | string | | | Additional class for outer wrapper |
230
+ | `internalClassName` | string | | | Class for inner content wrapper |
231
+
232
+ **Example**
233
+ ```tsx
234
+ <TransitionGroup component={null}>
235
+ {visible && (
236
+ <ExpandTransition key={0}>
237
+ <Card>Content</Card>
238
+ </ExpandTransition>
239
+ )}
240
+ </TransitionGroup>
241
+ ```
242
+
243
+ **Notes**
244
+ - Must be used with `TransitionGroup` from `react-transition-group`
245
+ - Creates zoom-in/zoom-out effect with position animation
246
+ - More complex than Collapse, use for special UI effects
247
+ - Requires `key` prop when used in TransitionGroup
248
+
249
+ ---
250
+
251
+ ## Hooks
252
+
253
+ ### useClickAway
254
+ - **Path:** `~/src/hooks/useClickAway`
255
+ - **Description:** Detects clicks outside a referenced element and triggers a callback
256
+ - **Signature:**
257
+ ```ts
258
+ useClickAway<T extends HTMLElement>(
259
+ use: boolean,
260
+ ref: RefObject<T | null>,
261
+ cb: (event: MouseEvent | TouchEvent) => void
262
+ ): void
263
+ ```
264
+
265
+ **Params:** `use` – enable/disable; `ref` – target element ref; `cb` – callback on outside click
266
+
267
+ **Returns:** `void`
268
+
269
+ **Example**
270
+ ```tsx
271
+ const ref = useRef<HTMLDivElement>(null);
272
+ useClickAway(isOpen, ref, () => setIsOpen(false));
273
+ ```
274
+
275
+ **Notes**
276
+ - Handles both mouse and touch events
277
+ - Callback is persisted to avoid re-subscriptions
278
+ - Can be toggled on/off with `use` parameter
279
+ - Perfect for dropdowns, modals, and popovers
280
+ - Automatically cleans up event listeners
281
+
282
+ ---
283
+
284
+ ### usePersistentFunction
285
+ - **Path:** `~/src/hooks/usePersistentFunction`
286
+ - **Description:** Creates a stable function reference that always calls the latest version of a callback
287
+ - **Signature:**
288
+ ```ts
289
+ usePersistentFunction<Args extends any[], Return>(
290
+ callback: (...args: Args) => Return
291
+ ): (...args: Args) => Return
292
+ ```
293
+
294
+ **Example**
295
+ ```tsx
296
+ const stableHandler = usePersistentFunction(() => {
297
+ console.log(currentState); // Always uses latest state
298
+ });
299
+ ```
300
+
301
+ **Notes**
302
+ - Prevents unnecessary re-renders in child components
303
+ - Always executes the latest callback version
304
+ - Alternative to `useCallback` with empty deps
305
+ - Ideal for event handlers and effect cleanup functions
306
+ - Maintains referential equality across renders
307
+
308
+ ---
309
+
310
+ ### useAsync
311
+ - **Path:** `~/src/hooks/useAsync`
312
+ - **Description:** Automatically executes an async function and manages its state
313
+ - **Signature:**
314
+ ```ts
315
+ useAsync<T extends FunctionReturningPromise>(fn: T, deps?: DependencyList): AsyncState<T>
316
+ ```
317
+
318
+ **Example**
319
+ ```tsx
320
+ const { loading, error, value } = useAsync(
321
+ () => fetchUserData(userId),
322
+ [userId]
323
+ );
324
+ ```
325
+
326
+ **Notes**
327
+ - Executes immediately on mount and when deps change
328
+ - Handles loading states automatically
329
+ - Catches and exposes errors
330
+ - Starts with `loading: true`
331
+ - Re-runs when dependencies change
332
+
333
+ ---
334
+
335
+ ### useAsyncFn
336
+ - **Path:** `~/src/hooks/useAsyncFn`
337
+ - **Description:** Returns an async function wrapper with state management
338
+ - **Signature:**
339
+ ```ts
340
+ useAsyncFn<T extends FunctionReturningPromise>(
341
+ fn: T,
342
+ deps?: DependencyList,
343
+ initialState?: AsyncState<T>
344
+ ): [AsyncState<T>, (p?: CallbackParams) => ReturnType<T>]
345
+ ```
346
+
347
+ **Example**
348
+ ```tsx
349
+ const [state, doFetch] = useAsyncFn(
350
+ () => api.saveData(data),
351
+ [data]
352
+ );
353
+ // Later: doFetch({ reset: true });
354
+ ```
355
+
356
+ **Notes**
357
+ - Manual trigger unlike `useAsync`
358
+ - Returns both state and executable function
359
+ - Supports `reset` option to clear previous state
360
+ - Prevents race conditions with call tracking
361
+ - Safe to use with unmounted components
362
+
363
+ ---
364
+
365
+ ### useAsyncFunction
366
+ - **Path:** `~/src/hooks/useAsyncFunction`
367
+ - **Description:** Creates a persistent async function with state tracking and call counting
368
+ - **Signature:**
369
+ ```ts
370
+ useAsyncFunction<T extends FunctionReturningPromise>(fn: T): [AsyncFunctionState<T>, T]
371
+ ```
372
+
373
+ **Example**
374
+ ```tsx
375
+ const [state, saveUser] = useAsyncFunction(async (user) => {
376
+ return api.saveUser(user);
377
+ });
378
+ // state.count tracks number of calls
379
+ ```
380
+
381
+ **Notes**
382
+ - Includes call counter in state
383
+ - Function reference is always stable
384
+ - Tracks loading state per call
385
+ - Useful for forms and repeated actions
386
+ - Prevents state updates on unmounted components
387
+
388
+ ---
389
+
390
+ ### useEffectOnce
391
+ - **Path:** `~/src/hooks/useEffectOnce`
392
+ - **Description:** Runs an effect only once on mount
393
+ - **Signature:** `useEffectOnce(effect: EffectCallback): void`
394
+
395
+ **Example**
396
+ ```tsx
397
+ useEffectOnce(() => {
398
+ initializeApp();
399
+ return () => cleanup();
400
+ });
401
+ ```
402
+
403
+ **Notes**
404
+ - Equivalent to `useEffect` with empty deps
405
+ - Cleaner API for one-time setup
406
+ - Supports cleanup function return
407
+ - Use for initialization logic
408
+
409
+ ---
410
+
411
+ ### useMount
412
+ - **Path:** `~/src/hooks/useMount`
413
+ - **Description:** Runs a function on component mount
414
+ - **Signature:** `useMount(fn: () => void): void`
415
+
416
+ **Example**
417
+ ```tsx
418
+ useMount(() => {
419
+ console.log('Component mounted');
420
+ });
421
+ ```
422
+
423
+ **Notes**
424
+ - Simpler than `useEffectOnce` when no cleanup needed
425
+ - Only for side effects on mount
426
+ - No return value expected
427
+
428
+ ---
429
+
430
+ ### useUnmount
431
+ - **Path:** `~/src/hooks/useUnmount`
432
+ - **Description:** Runs a function on component unmount
433
+ - **Signature:** `useUnmount(fn: () => any): void`
434
+
435
+ **Example**
436
+ ```tsx
437
+ useUnmount(() => {
438
+ saveProgress();
439
+ disconnectSocket();
440
+ });
441
+ ```
442
+
443
+ **Notes**
444
+ - Always uses latest version of cleanup function
445
+ - Perfect for cleanup operations
446
+ - Saves form data, closes connections, etc.
447
+ - Function ref is updated each render
448
+
449
+ ---
450
+
451
+ ### useMountedState
452
+ - **Path:** `~/src/hooks/useMountedState`
453
+ - **Description:** Returns a function that checks if component is still mounted
454
+ - **Signature:** `useMountedState(): () => boolean`
455
+
456
+ **Example**
457
+ ```tsx
458
+ const isMounted = useMountedState();
459
+
460
+ setTimeout(() => {
461
+ if (isMounted()) {
462
+ setState(newValue);
463
+ }
464
+ }, 1000);
465
+ ```
466
+
467
+ **Notes**
468
+ - Prevents state updates on unmounted components
469
+ - Essential for async operations
470
+ - Check before `setState` in callbacks
471
+ - Stable function reference
472
+
473
+ ---
474
+
475
+ ### useDelayedValue
476
+ - **Path:** `~/src/hooks/useDelayedValue`
477
+ - **Description:** Delays value updates by specified milliseconds
478
+ - **Signature:** `useDelayedValue<T>(value: T, delay: number): T`
479
+
480
+ **Example**
481
+ ```tsx
482
+ const debouncedSearch = useDelayedValue(searchTerm, 300);
483
+ useEffect(() => {
484
+ performSearch(debouncedSearch);
485
+ }, [debouncedSearch]);
486
+ ```
487
+
488
+ **Notes**
489
+ - Acts as a debounce for value changes
490
+ - Useful for search inputs and API calls
491
+ - Cancels pending updates on new values
492
+ - Returns initial value immediately
493
+
494
+ ---
495
+
496
+ ### useDelayedEffect
497
+ - **Path:** `~/src/hooks/useDelayedEffect`
498
+ - **Description:** Runs an effect after a delay with cancellation support
499
+ - **Signature:**
500
+ ```ts
501
+ useDelayedEffect(
502
+ effect: (getIsRunning: () => boolean) => void,
503
+ deps: DependencyList,
504
+ delay: number
505
+ ): void
506
+ ```
507
+
508
+ **Example**
509
+ ```tsx
510
+ useDelayedEffect(
511
+ (getIsRunning) => {
512
+ if (getIsRunning()) {
513
+ performExpensiveOperation();
514
+ }
515
+ },
516
+ [data],
517
+ 500
518
+ );
519
+ ```
520
+
521
+ **Notes**
522
+ - Provides cancellation check via `getIsRunning`
523
+ - Clears timeout on deps change or unmount
524
+ - Useful for debounced side effects
525
+ - Check `getIsRunning()` before state updates
526
+
527
+ ---
528
+
529
+ ## Utilities
530
+
531
+ ### formatNumber
532
+ - **Path:** `~/src/formatNumber`
533
+ - **Description:** Formats numbers with spacing or short notation
534
+ - **Signature:** `formatNumber(number: number, format: 'spaced' | 'short'): string`
535
+
536
+ **Examples**
537
+ ```ts
538
+ formatNumber(1234567, 'spaced') // '1 234 567'
539
+ formatNumber(1234567, 'short') // '1.2M'
540
+ formatNumber(999, 'short') // '999'
541
+ ```
542
+
543
+ **Notes**
544
+ - `spaced`: Adds spaces every 3 digits
545
+ - `short`: Uses K, M, B notation above 10,000
546
+ - Short format keeps one decimal place
547
+ - Numbers below 10K in short format use spaced display
548
+ - Ideal for statistics and dashboards
549
+
550
+ ---
551
+
552
+ ### toCssValue
553
+ - **Path:** `~/src/grid`
554
+ - **Description:** Converts number to px string or returns string as-is
555
+ - **Signature:** `toCssValue(value: string | number): string`
556
+
557
+ **Examples**
558
+ ```ts
559
+ toCssValue(16) // '16px'
560
+ toCssValue('2rem') // '2rem'
561
+ ```
562
+
563
+ **Notes**
564
+ - Used internally by grid components
565
+ - Numbers become pixels
566
+ - Strings pass through unchanged
567
+ - Enables flexible prop values
568
+
569
+ ---
570
+
571
+ ### elementAddListeners
572
+ - **Path:** `~/src/hooks/useClickAway`
573
+ - **Description:** Adds event listeners to elements with cleanup function
574
+ - **Signature:**
575
+ ```ts
576
+ elementAddListeners<
577
+ K extends keyof HTMLElementEventMap,
578
+ E extends HTMLElement
579
+ >(element: E | Document, eventNames: K | K[], listener: EventListener): () => void
580
+ ```
581
+
582
+ **Example**
583
+ ```ts
584
+ const cleanup = elementAddListeners(
585
+ document,
586
+ ['mousedown', 'touchstart'],
587
+ handleClick
588
+ );
589
+ ```
590
+
591
+ **Notes**
592
+ - Returns cleanup function
593
+ - Supports multiple event types
594
+ - Works with `Document` or `HTMLElement`
595
+ - Used internally by `useClickAway`
596
+
597
+ ---
598
+
599
+ ### subscribeClickAway
600
+ - **Path:** `~/src/hooks/useClickAway`
601
+ - **Description:** Subscribes to clicks outside an element
602
+ - **Signature:**
603
+ ```ts
604
+ subscribeClickAway<T extends HTMLElement>(
605
+ element: T,
606
+ callback: (event: MouseEvent | TouchEvent) => void
607
+ ): () => void
608
+ ```
609
+
610
+ **Example**
611
+ ```ts
612
+ const unsubscribe = subscribeClickAway(dropdownEl, () => closeDropdown());
613
+ ```
614
+
615
+ **Notes**
616
+ - Standalone version of `useClickAway` logic
617
+ - Returns unsubscribe function
618
+ - Handles mouse and touch events
619
+ - Can be used outside React components
620
+
621
+ ---
622
+
623
+ ## Types
624
+
625
+ ### StrNum
626
+ - **Definition:** `type StrNum = string | number`
627
+ - **Description:** Union type for CSS values that can be string or number
628
+
629
+ ### CssBoxSides
630
+ - **Definition:**
631
+ ```ts
632
+ type CssBoxSides<T> =
633
+ | { top?: T; right?: T; bottom?: T; left?: T }
634
+ | { horizontal: T; top?: T; bottom?: T }
635
+ | { vertical: T; right?: T; left?: T }
636
+ ```
637
+ - **Description:** Flexible type for directional CSS properties like margin/padding
638
+
639
+ ### AsyncState
640
+ - **Definition:** `type AsyncState<T> = { loading: boolean; error?: Error; value?: T }`
641
+ - **Description:** State shape for async operations with loading, error, and value
642
+
643
+ ### FunctionReturningPromise
644
+ - **Definition:** `type FunctionReturningPromise = (...args: any[]) => Promise<any>`
645
+ - **Description:** Generic type for any async function
646
+
647
+ ### PromiseType
648
+ - **Definition:** `type PromiseType<P extends Promise<any>> = P extends Promise<infer T> ? T : never`
649
+ - **Description:** Extracts the resolved type from a Promise
650
+
651
+ ---
652
+
653
+ ## Patterns
654
+
655
+ ### styledComponentProps
656
+ - **Description:** All styled-component props are prefixed with `$` to avoid React prop warnings
657
+ - **Example:** `$gap, $padding, $margin` instead of `gap, padding, margin`
658
+
659
+ ### lazyRendering
660
+ - **Description:** Components accept function children for deferred rendering
661
+ - **Example:** `<Collapse expanded={show}>{() => <ExpensiveComponent />}</Collapse>`
662
+
663
+ ### persistentCallbacks
664
+ - **Description:** Use `usePersistentFunction` to maintain stable references while using latest closure values
665
+ - **Example:** `const stable = usePersistentFunction(callback)`
666
+
667
+ ### asyncStateManagement
668
+ - **Description:** Consistent async state shape with loading, error, and value properties
669
+ - **Example:** All async hooks return `{ loading, error, value }` state
670
+
671
+ ---
672
+
673
+ ## Best Practices
674
+ - Use specific layout components (Columns, Horizontal, Vertical) over generic GridContainer
675
+ - Prefix all styled-component props with `$` to avoid DOM prop warnings
676
+ - Use `usePersistentFunction` for callbacks passed to memoized components
677
+ - Check `isMounted()` before state updates in async operations
678
+ - Use function children in `Collapse` for expensive components
679
+ - Leverage TypeScript's discriminated unions in `AsyncState` for type safety
680
+ - Use `useDelayedValue` for debouncing user input
681
+ - Combine `useClickAway` with portals for robust dropdown behavior
@@ -1,3 +1,27 @@
1
- export declare function elementAddListener<K extends keyof HTMLElementEventMap, E extends HTMLElement>(element: E, eventNames: K | K[], listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => void): () => void;
2
- export declare function subscribeClickAway<T extends HTMLElement>(element: T, callback: EventListener): () => void;
3
- export declare function useClickAway(use: boolean, ref: any, cb: EventListener): void;
1
+ import { type RefObject } from 'react';
2
+ type ClickAwayCallback = (event: MouseEvent | TouchEvent) => void;
3
+ /**
4
+ * Adds event listeners to an element and returns a cleanup function.
5
+ * @param element The target HTML element.
6
+ * @param eventNames The event name(s) to listen for.
7
+ * @param listener The callback function to execute when the event occurs.
8
+ * @returns A function to remove the added event listeners.
9
+ */
10
+ export declare function elementAddListeners<K extends keyof HTMLElementEventMap, E extends HTMLElement>(element: E | Document, // Allow Document as well
11
+ eventNames: K | K[], listener: (this: E | Document, ev: HTMLElementEventMap[K]) => void): () => void;
12
+ /**
13
+ * Subscribes to mousedown/touchstart events outside a given element.
14
+ * @param element The element to detect clicks away from.
15
+ * @param callback The function to call when a click away is detected.
16
+ * @returns A function to unsubscribe the listeners.
17
+ */
18
+ export declare function subscribeClickAway<T extends HTMLElement>(element: T, callback: ClickAwayCallback): () => void;
19
+ /**
20
+ * Custom hook to trigger a callback when a click occurs outside the referenced element.
21
+ * @param use Whether the hook is active.
22
+ * @param ref A React ref object pointing to the target HTMLElement.
23
+ * @param cb The callback function to execute on a click away.
24
+ */
25
+ export declare function useClickAway<T extends HTMLElement>(use: boolean, ref: RefObject<T | null>, // Use RefObject<T | null> for better type safety
26
+ cb: ClickAwayCallback): void;
27
+ export {};
@@ -1,28 +1,59 @@
1
1
  import { useEffect } from 'react';
2
2
  import usePersistentFunction from './usePersistentFunction';
3
- export function elementAddListener(element, eventNames, listener) {
3
+ /**
4
+ * Adds event listeners to an element and returns a cleanup function.
5
+ * @param element The target HTML element.
6
+ * @param eventNames The event name(s) to listen for.
7
+ * @param listener The callback function to execute when the event occurs.
8
+ * @returns A function to remove the added event listeners.
9
+ */
10
+ export function elementAddListeners(element,
11
+ // Allow Document as well
12
+ eventNames, listener) {
4
13
  const eventNamesArray = Array.isArray(eventNames) ? eventNames : [eventNames];
5
14
  eventNamesArray.forEach(eventName => {
6
- element.addEventListener(eventName, listener);
15
+ element.addEventListener(eventName, listener); // Cast needed as addEventListener expects EventListener
7
16
  });
8
17
  return () => {
9
18
  eventNamesArray.forEach(eventName => {
10
- element.removeEventListener(eventName, listener);
19
+ element.removeEventListener(eventName, listener); // Cast needed as removeEventListener expects EventListener
11
20
  });
12
21
  };
13
22
  }
23
+ /**
24
+ * Subscribes to mousedown/touchstart events outside a given element.
25
+ * @param element The element to detect clicks away from.
26
+ * @param callback The function to call when a click away is detected.
27
+ * @returns A function to unsubscribe the listeners.
28
+ */
14
29
  export function subscribeClickAway(element, callback) {
15
- return elementAddListener(document, ['mousedown', 'touchstart'], event => {
16
- if (element && !element.contains(event.target)) {
17
- callback(event);
30
+ // Use document directly, no need for 'any' cast
31
+ return elementAddListeners(document, ['mousedown', 'touchstart'], event => {
32
+ // Type assertion for event target
33
+ const target = event.target;
34
+ // Check if the element exists and the click target is outside the element
35
+ if (element && !element.contains(target)) {
36
+ callback(event); // Assert event type for the callback
18
37
  }
19
38
  });
20
39
  }
21
- export function useClickAway(use, ref, cb) {
22
- const mcb = usePersistentFunction(cb);
40
+ /**
41
+ * Custom hook to trigger a callback when a click occurs outside the referenced element.
42
+ * @param use Whether the hook is active.
43
+ * @param ref A React ref object pointing to the target HTMLElement.
44
+ * @param cb The callback function to execute on a click away.
45
+ */
46
+ export function useClickAway(use, ref,
47
+ // Use RefObject<T | null> for better type safety
48
+ cb) {
49
+ // Persist the callback function to avoid re-subscribing on every render
50
+ const persistentCb = usePersistentFunction(cb);
51
+ // eslint-disable-next-line consistent-return
23
52
  useEffect(() => {
24
- if (use) {
25
- return subscribeClickAway(ref.current, mcb);
53
+ // Only subscribe if the hook is active and the ref points to an element
54
+ if (use && ref.current) {
55
+ // Return the cleanup function directly
56
+ return subscribeClickAway(ref.current, persistentCb);
26
57
  }
27
- }, [use, ref, mcb]);
58
+ }, [use, ref, persistentCb]);
28
59
  }
@@ -1 +1,47 @@
1
- export default function usePersistentFunction<T extends (...args: any[]) => any>(callback: T): T;
1
+ /**
2
+ * Creates a stable function reference that always calls the latest version of a callback.
3
+ *
4
+ * This hook is useful when you need to pass a callback to a child component or as a dependency
5
+ * to another hook, but want to avoid unnecessary re-renders when the callback changes.
6
+ *
7
+ * Unlike using plain `useCallback`, this approach ensures that the latest version of the
8
+ * callback is always used, even if the reference remains stable.
9
+ *
10
+ * @param callback - The function that should be persisted
11
+ * @returns A stable function reference that will always call the most recent version of the provided callback
12
+ *
13
+ * @example
14
+ * // Using with event handlers
15
+ * const MyComponent = () => {
16
+ * const [count, setCount] = useState(0);
17
+ *
18
+ * // This function reference remains stable across renders
19
+ * // but will always use the current value of `count`
20
+ * const handleClick = usePersistentFunction(() => {
21
+ * console.log(`Current count: ${count}`);
22
+ * });
23
+ *
24
+ * return <ChildComponent onClick={handleClick} />;
25
+ * };
26
+ *
27
+ * @example
28
+ * // Using with cleanup functions
29
+ * const MyComponent = () => {
30
+ * const [resource, setResource] = useState(null);
31
+ *
32
+ * // The cleanup function remains stable but always uses the latest resource
33
+ * const cleanup = usePersistentFunction(() => {
34
+ * if (resource) {
35
+ * resource.dispose();
36
+ * }
37
+ * });
38
+ *
39
+ * useEffect(() => {
40
+ * // Some setup code...
41
+ * return cleanup;
42
+ * }, []); // No need to include cleanup in dependencies
43
+ *
44
+ * return <div>...</div>;
45
+ * };
46
+ */
47
+ export default function usePersistentFunction<Args extends any[], Return>(callback: (...args: Args) => Return): (...args: Args) => Return;
@@ -1,6 +1,55 @@
1
1
  import { useCallback, useRef } from 'react';
2
+ /**
3
+ * Creates a stable function reference that always calls the latest version of a callback.
4
+ *
5
+ * This hook is useful when you need to pass a callback to a child component or as a dependency
6
+ * to another hook, but want to avoid unnecessary re-renders when the callback changes.
7
+ *
8
+ * Unlike using plain `useCallback`, this approach ensures that the latest version of the
9
+ * callback is always used, even if the reference remains stable.
10
+ *
11
+ * @param callback - The function that should be persisted
12
+ * @returns A stable function reference that will always call the most recent version of the provided callback
13
+ *
14
+ * @example
15
+ * // Using with event handlers
16
+ * const MyComponent = () => {
17
+ * const [count, setCount] = useState(0);
18
+ *
19
+ * // This function reference remains stable across renders
20
+ * // but will always use the current value of `count`
21
+ * const handleClick = usePersistentFunction(() => {
22
+ * console.log(`Current count: ${count}`);
23
+ * });
24
+ *
25
+ * return <ChildComponent onClick={handleClick} />;
26
+ * };
27
+ *
28
+ * @example
29
+ * // Using with cleanup functions
30
+ * const MyComponent = () => {
31
+ * const [resource, setResource] = useState(null);
32
+ *
33
+ * // The cleanup function remains stable but always uses the latest resource
34
+ * const cleanup = usePersistentFunction(() => {
35
+ * if (resource) {
36
+ * resource.dispose();
37
+ * }
38
+ * });
39
+ *
40
+ * useEffect(() => {
41
+ * // Some setup code...
42
+ * return cleanup;
43
+ * }, []); // No need to include cleanup in dependencies
44
+ *
45
+ * return <div>...</div>;
46
+ * };
47
+ */
2
48
  export default function usePersistentFunction(callback) {
3
- const ref = useRef(callback);
4
- ref.current = callback;
5
- return useCallback((...args) => ref.current(...args), []);
49
+ // Store the latest callback in a ref
50
+ const callbackRef = useRef(callback);
51
+ // Update the ref to the latest callback on each render
52
+ callbackRef.current = callback;
53
+ // Create a stable function reference that calls the latest callback
54
+ return useCallback((...args) => callbackRef.current(...args), []);
6
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushwoosh/kit-helpers",
3
- "version": "4.8.2",
3
+ "version": "4.8.4",
4
4
  "description": "React component for build Pushwoosh products",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -8,7 +8,7 @@
8
8
  "sideEffects": false,
9
9
  "scripts": {
10
10
  "start": "npm run playground",
11
- "build": "pushwoosh-engine lib:build",
11
+ "build": "pushwoosh-engine lib:build && cp ai-documentation.mdx dist/ai-documentation.mdx",
12
12
  "playground": "pushwoosh-engine lib:playground",
13
13
  "check": "npm run check:lint && npm run check:types",
14
14
  "check:lint": "echo \"Run check:lint\" && eslint .",