@teo-garcia/react-shared 0.1.9 → 1.2.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.
Files changed (177) hide show
  1. package/README.md +127 -99
  2. package/dist/components/aspect-ratio/aspect-ratio.d.ts +17 -0
  3. package/dist/components/aspect-ratio/aspect-ratio.d.ts.map +1 -0
  4. package/dist/components/aspect-ratio/aspect-ratio.js +14 -0
  5. package/dist/components/aspect-ratio/index.d.ts +2 -0
  6. package/dist/components/aspect-ratio/index.d.ts.map +1 -0
  7. package/dist/components/aspect-ratio/index.js +1 -0
  8. package/dist/components/debug-json/debug-json.d.ts +14 -0
  9. package/dist/components/debug-json/debug-json.d.ts.map +1 -0
  10. package/dist/components/debug-json/debug-json.js +32 -0
  11. package/dist/components/debug-json/index.d.ts +2 -0
  12. package/dist/components/debug-json/index.d.ts.map +1 -0
  13. package/dist/components/debug-json/index.js +1 -0
  14. package/dist/components/dev-panel/dev-panel.d.ts +30 -0
  15. package/dist/components/dev-panel/dev-panel.d.ts.map +1 -0
  16. package/dist/components/dev-panel/dev-panel.js +155 -0
  17. package/dist/components/dev-panel/index.d.ts +2 -0
  18. package/dist/components/dev-panel/index.d.ts.map +1 -0
  19. package/dist/components/dev-panel/index.js +1 -0
  20. package/dist/components/error-boundary/error-boundary.d.ts +3 -58
  21. package/dist/components/error-boundary/error-boundary.d.ts.map +1 -1
  22. package/dist/components/error-boundary/error-boundary.js +51 -77
  23. package/dist/components/error-boundary/index.d.ts +1 -0
  24. package/dist/components/error-boundary/index.d.ts.map +1 -1
  25. package/dist/components/focus-trap/focus-trap.d.ts +16 -0
  26. package/dist/components/focus-trap/focus-trap.d.ts.map +1 -0
  27. package/dist/components/focus-trap/focus-trap.js +57 -0
  28. package/dist/components/focus-trap/index.d.ts +2 -0
  29. package/dist/components/focus-trap/index.d.ts.map +1 -0
  30. package/dist/components/focus-trap/index.js +1 -0
  31. package/dist/components/index.d.ts +9 -5
  32. package/dist/components/index.d.ts.map +1 -1
  33. package/dist/components/index.js +9 -5
  34. package/dist/components/portal/index.d.ts +2 -0
  35. package/dist/components/portal/index.d.ts.map +1 -0
  36. package/dist/components/portal/index.js +1 -0
  37. package/dist/components/portal/portal.d.ts +14 -0
  38. package/dist/components/portal/portal.d.ts.map +1 -0
  39. package/dist/components/portal/portal.js +21 -0
  40. package/dist/components/separator/index.d.ts +2 -0
  41. package/dist/components/separator/index.d.ts.map +1 -0
  42. package/dist/components/separator/index.js +1 -0
  43. package/dist/components/separator/separator.d.ts +11 -0
  44. package/dist/components/separator/separator.d.ts.map +1 -0
  45. package/dist/components/separator/separator.js +11 -0
  46. package/dist/components/skeleton/index.d.ts +2 -0
  47. package/dist/components/skeleton/index.d.ts.map +1 -0
  48. package/dist/components/skeleton/index.js +1 -0
  49. package/dist/components/skeleton/skeleton.d.ts +3 -0
  50. package/dist/components/skeleton/skeleton.d.ts.map +1 -0
  51. package/dist/components/skeleton/skeleton.js +5 -0
  52. package/dist/components/skip-link/index.d.ts +2 -0
  53. package/dist/components/skip-link/index.d.ts.map +1 -0
  54. package/dist/components/skip-link/index.js +1 -0
  55. package/dist/components/skip-link/skip-link.d.ts +13 -0
  56. package/dist/components/skip-link/skip-link.d.ts.map +1 -0
  57. package/dist/components/skip-link/skip-link.js +26 -0
  58. package/dist/components/visually-hidden/index.d.ts +2 -0
  59. package/dist/components/visually-hidden/index.d.ts.map +1 -0
  60. package/dist/components/visually-hidden/index.js +1 -0
  61. package/dist/components/visually-hidden/visually-hidden.d.ts +3 -0
  62. package/dist/components/visually-hidden/visually-hidden.d.ts.map +1 -0
  63. package/dist/components/visually-hidden/visually-hidden.js +15 -0
  64. package/dist/hooks/index.d.ts +15 -5
  65. package/dist/hooks/index.d.ts.map +1 -1
  66. package/dist/hooks/index.js +15 -4
  67. package/dist/hooks/use-copy-to-clipboard.d.ts +7 -0
  68. package/dist/hooks/use-copy-to-clipboard.d.ts.map +1 -0
  69. package/dist/hooks/use-copy-to-clipboard.js +23 -0
  70. package/dist/hooks/use-debounce.d.ts +2 -0
  71. package/dist/hooks/use-debounce.d.ts.map +1 -0
  72. package/dist/hooks/use-debounce.js +9 -0
  73. package/dist/hooks/use-event-listener.d.ts +4 -0
  74. package/dist/hooks/use-event-listener.d.ts.map +1 -0
  75. package/dist/hooks/use-event-listener.js +13 -0
  76. package/dist/hooks/use-idle.d.ts +7 -0
  77. package/dist/hooks/use-idle.d.ts.map +1 -0
  78. package/dist/hooks/use-idle.js +35 -0
  79. package/dist/hooks/use-intersection-observer.d.ts +15 -0
  80. package/dist/hooks/use-intersection-observer.d.ts.map +1 -0
  81. package/dist/hooks/use-intersection-observer.js +22 -0
  82. package/dist/hooks/use-isomorphic-layout-effect.d.ts +3 -0
  83. package/dist/hooks/use-isomorphic-layout-effect.d.ts.map +1 -0
  84. package/dist/hooks/use-isomorphic-layout-effect.js +4 -0
  85. package/dist/hooks/use-latest.d.ts +7 -0
  86. package/dist/hooks/use-latest.d.ts.map +1 -0
  87. package/dist/hooks/use-latest.js +11 -0
  88. package/dist/hooks/use-local-storage.d.ts +2 -0
  89. package/dist/hooks/use-local-storage.d.ts.map +1 -0
  90. package/dist/hooks/use-local-storage.js +37 -0
  91. package/dist/hooks/use-media-query.d.ts +2 -0
  92. package/dist/hooks/use-media-query.d.ts.map +1 -0
  93. package/dist/hooks/use-media-query.js +18 -0
  94. package/dist/hooks/use-network-status.d.ts +10 -0
  95. package/dist/hooks/use-network-status.d.ts.map +1 -0
  96. package/dist/hooks/use-network-status.js +19 -0
  97. package/dist/hooks/use-on-click-outside.d.ts +3 -0
  98. package/dist/hooks/use-on-click-outside.d.ts.map +1 -0
  99. package/dist/hooks/use-on-click-outside.js +17 -0
  100. package/dist/hooks/use-previous.d.ts +2 -0
  101. package/dist/hooks/use-previous.d.ts.map +1 -0
  102. package/dist/hooks/use-previous.js +8 -0
  103. package/dist/hooks/use-render-count.d.ts +7 -0
  104. package/dist/hooks/use-render-count.d.ts.map +1 -0
  105. package/dist/hooks/use-render-count.js +15 -0
  106. package/dist/hooks/use-toggle.d.ts +6 -0
  107. package/dist/hooks/use-toggle.d.ts.map +1 -0
  108. package/dist/hooks/use-toggle.js +12 -0
  109. package/dist/hooks/use-why-did-you-render.d.ts +8 -0
  110. package/dist/hooks/use-why-did-you-render.d.ts.map +1 -0
  111. package/dist/hooks/use-why-did-you-render.js +38 -0
  112. package/dist/index.d.ts +6 -4
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +6 -9
  115. package/dist/test-utils/index.d.ts +14 -0
  116. package/dist/test-utils/index.d.ts.map +1 -0
  117. package/dist/test-utils/index.js +22 -0
  118. package/dist/types.d.ts +17 -36
  119. package/dist/types.d.ts.map +1 -1
  120. package/dist/utils/cn.d.ts +3 -0
  121. package/dist/utils/cn.d.ts.map +1 -0
  122. package/dist/utils/cn.js +5 -0
  123. package/dist/utils/format-date.d.ts +11 -0
  124. package/dist/utils/format-date.d.ts.map +1 -0
  125. package/dist/utils/format-date.js +12 -0
  126. package/dist/utils/format-number.d.ts +11 -0
  127. package/dist/utils/format-number.d.ts.map +1 -0
  128. package/dist/utils/format-number.js +12 -0
  129. package/dist/utils/index.d.ts +4 -5
  130. package/dist/utils/index.d.ts.map +1 -1
  131. package/dist/utils/index.js +4 -5
  132. package/dist/utils/truncate.d.ts +11 -0
  133. package/dist/utils/truncate.d.ts.map +1 -0
  134. package/dist/utils/truncate.js +14 -0
  135. package/package.json +141 -42
  136. package/dist/adapters/environment/index.d.ts +0 -6
  137. package/dist/adapters/environment/index.d.ts.map +0 -1
  138. package/dist/adapters/environment/index.js +0 -5
  139. package/dist/adapters/environment/next.d.ts +0 -17
  140. package/dist/adapters/environment/next.d.ts.map +0 -1
  141. package/dist/adapters/environment/next.js +0 -20
  142. package/dist/adapters/environment/vite.d.ts +0 -17
  143. package/dist/adapters/environment/vite.d.ts.map +0 -1
  144. package/dist/adapters/environment/vite.js +0 -20
  145. package/dist/adapters/index.d.ts +0 -9
  146. package/dist/adapters/index.d.ts.map +0 -1
  147. package/dist/adapters/index.js +0 -8
  148. package/dist/adapters/theme/custom.d.ts +0 -32
  149. package/dist/adapters/theme/custom.d.ts.map +0 -1
  150. package/dist/adapters/theme/custom.js +0 -26
  151. package/dist/adapters/theme/index.d.ts +0 -6
  152. package/dist/adapters/theme/index.d.ts.map +0 -1
  153. package/dist/adapters/theme/index.js +0 -5
  154. package/dist/adapters/theme/next-themes.d.ts +0 -18
  155. package/dist/adapters/theme/next-themes.d.ts.map +0 -1
  156. package/dist/adapters/theme/next-themes.js +0 -23
  157. package/dist/components/theme-switch/index.d.ts +0 -3
  158. package/dist/components/theme-switch/index.d.ts.map +0 -1
  159. package/dist/components/theme-switch/index.js +0 -1
  160. package/dist/components/theme-switch/theme-switch.d.ts +0 -36
  161. package/dist/components/theme-switch/theme-switch.d.ts.map +0 -1
  162. package/dist/components/theme-switch/theme-switch.js +0 -74
  163. package/dist/components/viewport-info/index.d.ts +0 -3
  164. package/dist/components/viewport-info/index.d.ts.map +0 -1
  165. package/dist/components/viewport-info/index.js +0 -1
  166. package/dist/components/viewport-info/viewport-info.d.ts +0 -40
  167. package/dist/components/viewport-info/viewport-info.d.ts.map +0 -1
  168. package/dist/components/viewport-info/viewport-info.js +0 -69
  169. package/dist/hooks/use-healthcheck.d.ts +0 -42
  170. package/dist/hooks/use-healthcheck.d.ts.map +0 -1
  171. package/dist/hooks/use-healthcheck.js +0 -53
  172. package/dist/utils/environment.d.ts +0 -71
  173. package/dist/utils/environment.d.ts.map +0 -1
  174. package/dist/utils/environment.js +0 -86
  175. package/dist/utils/msw.d.ts +0 -54
  176. package/dist/utils/msw.d.ts.map +0 -1
  177. package/dist/utils/msw.js +0 -62
@@ -1,4 +1,15 @@
1
- /**
2
- * Hooks - Reusable React hooks for common functionality
3
- */
4
- export { useHealthcheck } from './use-healthcheck.js';
1
+ export { useCopyToClipboard } from './use-copy-to-clipboard.js';
2
+ export { useDebounce } from './use-debounce.js';
3
+ export { useEventListener } from './use-event-listener.js';
4
+ export { useIdle } from './use-idle.js';
5
+ export { useIntersectionObserver } from './use-intersection-observer.js';
6
+ export { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect.js';
7
+ export { useLatest } from './use-latest.js';
8
+ export { useLocalStorage } from './use-local-storage.js';
9
+ export { useMediaQuery } from './use-media-query.js';
10
+ export { useNetworkStatus } from './use-network-status.js';
11
+ export { useOnClickOutside } from './use-on-click-outside.js';
12
+ export { usePrevious } from './use-previous.js';
13
+ export { useRenderCount } from './use-render-count.js';
14
+ export { useToggle } from './use-toggle.js';
15
+ export { useWhyDidYouRender } from './use-why-did-you-render.js';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Returns `[copied, copy]`.
3
+ * `copy(text)` writes to the clipboard and returns a boolean indicating success.
4
+ * `copied` resets to `false` after `resetDelay` ms.
5
+ */
6
+ export declare function useCopyToClipboard(resetDelay?: number): [boolean, (text: string) => Promise<boolean>];
7
+ //# sourceMappingURL=use-copy-to-clipboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-copy-to-clipboard.d.ts","sourceRoot":"","sources":["../../src/hooks/use-copy-to-clipboard.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,SAAO,GAChB,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAoB/C"}
@@ -0,0 +1,23 @@
1
+ import { useCallback, useState } from 'react';
2
+ /**
3
+ * Returns `[copied, copy]`.
4
+ * `copy(text)` writes to the clipboard and returns a boolean indicating success.
5
+ * `copied` resets to `false` after `resetDelay` ms.
6
+ */
7
+ export function useCopyToClipboard(resetDelay = 2000) {
8
+ const [copied, setCopied] = useState(false);
9
+ const copy = useCallback(async (text) => {
10
+ if (!navigator.clipboard)
11
+ return false;
12
+ try {
13
+ await navigator.clipboard.writeText(text);
14
+ setCopied(true);
15
+ setTimeout(() => setCopied(false), resetDelay);
16
+ return true;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }, [resetDelay]);
22
+ return [copied, copy];
23
+ }
@@ -0,0 +1,2 @@
1
+ export declare function useDebounce<T>(value: T, delay: number): T;
2
+ //# sourceMappingURL=use-debounce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-debounce.d.ts","sourceRoot":"","sources":["../../src/hooks/use-debounce.ts"],"names":[],"mappings":"AAEA,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,CASzD"}
@@ -0,0 +1,9 @@
1
+ import { useEffect, useState } from 'react';
2
+ export function useDebounce(value, delay) {
3
+ const [debouncedValue, setDebouncedValue] = useState(value);
4
+ useEffect(() => {
5
+ const timer = setTimeout(() => setDebouncedValue(value), delay);
6
+ return () => clearTimeout(timer);
7
+ }, [value, delay]);
8
+ return debouncedValue;
9
+ }
@@ -0,0 +1,4 @@
1
+ export declare function useEventListener<K extends keyof WindowEventMap>(target: Window | null, event: K, handler: (event: WindowEventMap[K]) => void, options?: AddEventListenerOptions): void;
2
+ export declare function useEventListener<K extends keyof DocumentEventMap>(target: Document | null, event: K, handler: (event: DocumentEventMap[K]) => void, options?: AddEventListenerOptions): void;
3
+ export declare function useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement>(target: T | null, event: K, handler: (event: HTMLElementEventMap[K]) => void, options?: AddEventListenerOptions): void;
4
+ //# sourceMappingURL=use-event-listener.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-event-listener.d.ts","sourceRoot":"","sources":["../../src/hooks/use-event-listener.ts"],"names":[],"mappings":"AAKA,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,cAAc,EAC7D,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,EAC3C,OAAO,CAAC,EAAE,uBAAuB,GAChC,IAAI,CAAA;AACP,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAC/D,MAAM,EAAE,QAAQ,GAAG,IAAI,EACvB,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,IAAI,EAC7C,OAAO,CAAC,EAAE,uBAAuB,GAChC,IAAI,CAAA;AACP,wBAAgB,gBAAgB,CAC9B,CAAC,SAAS,MAAM,mBAAmB,EACnC,CAAC,SAAS,WAAW,EAErB,MAAM,EAAE,CAAC,GAAG,IAAI,EAChB,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,IAAI,EAChD,OAAO,CAAC,EAAE,uBAAuB,GAChC,IAAI,CAAA"}
@@ -0,0 +1,13 @@
1
+ import { useEffect } from 'react';
2
+ import { useLatest } from './use-latest.js';
3
+ export function useEventListener(target, event, handler, options) {
4
+ const handlerRef = useLatest(handler);
5
+ useEffect(() => {
6
+ if (!target)
7
+ return;
8
+ const listener = (e) => handlerRef.current(e);
9
+ target.addEventListener(event, listener, options);
10
+ return () => target.removeEventListener(event, listener, options);
11
+ // eslint-disable-next-line react-hooks/exhaustive-deps
12
+ }, [target, event, JSON.stringify(options)]);
13
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Returns `true` when the user has been inactive for longer than `timeout` ms.
3
+ * Resets on any mouse, keyboard, touch, or scroll event.
4
+ * Use for session-expiry warnings, pausing background work, or screensaver effects.
5
+ */
6
+ export declare function useIdle(timeout: number): boolean;
7
+ //# sourceMappingURL=use-idle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-idle.d.ts","sourceRoot":"","sources":["../../src/hooks/use-idle.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAyBhD"}
@@ -0,0 +1,35 @@
1
+ import { useEffect, useState } from 'react';
2
+ const ACTIVITY_EVENTS = [
3
+ 'mousemove',
4
+ 'keydown',
5
+ 'mousedown',
6
+ 'touchstart',
7
+ 'scroll',
8
+ 'wheel',
9
+ ];
10
+ /**
11
+ * Returns `true` when the user has been inactive for longer than `timeout` ms.
12
+ * Resets on any mouse, keyboard, touch, or scroll event.
13
+ * Use for session-expiry warnings, pausing background work, or screensaver effects.
14
+ */
15
+ export function useIdle(timeout) {
16
+ const [idle, setIdle] = useState(false);
17
+ useEffect(() => {
18
+ let timer = setTimeout(() => setIdle(true), timeout);
19
+ function handleActivity() {
20
+ clearTimeout(timer);
21
+ setIdle(false);
22
+ timer = setTimeout(() => setIdle(true), timeout);
23
+ }
24
+ for (const event of ACTIVITY_EVENTS) {
25
+ window.addEventListener(event, handleActivity, { passive: true });
26
+ }
27
+ return () => {
28
+ clearTimeout(timer);
29
+ for (const event of ACTIVITY_EVENTS) {
30
+ window.removeEventListener(event, handleActivity);
31
+ }
32
+ };
33
+ }, [timeout]);
34
+ return idle;
35
+ }
@@ -0,0 +1,15 @@
1
+ import { type RefObject } from 'react';
2
+ interface UseIntersectionObserverOptions extends IntersectionObserverInit {
3
+ /**
4
+ * Once the element is visible, stop observing and keep the entry frozen.
5
+ * Useful for lazy-load triggers that should fire only once.
6
+ */
7
+ freezeOnceVisible?: boolean;
8
+ }
9
+ /**
10
+ * Tracks whether an element is within the viewport (or a custom root).
11
+ * Returns the latest `IntersectionObserverEntry`, or `null` before first observation.
12
+ */
13
+ export declare function useIntersectionObserver(ref: RefObject<Element | null>, { threshold, root, rootMargin, freezeOnceVisible, }?: UseIntersectionObserverOptions): IntersectionObserverEntry | null;
14
+ export {};
15
+ //# sourceMappingURL=use-intersection-observer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-intersection-observer.d.ts","sourceRoot":"","sources":["../../src/hooks/use-intersection-observer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAE3D,UAAU,8BAA+B,SAAQ,wBAAwB;IACvE;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC,EAC9B,EACE,SAAa,EACb,IAAW,EACX,UAAiB,EACjB,iBAAyB,GAC1B,GAAE,8BAAmC,GACrC,yBAAyB,GAAG,IAAI,CAoBlC"}
@@ -0,0 +1,22 @@
1
+ import { useEffect, useState } from 'react';
2
+ /**
3
+ * Tracks whether an element is within the viewport (or a custom root).
4
+ * Returns the latest `IntersectionObserverEntry`, or `null` before first observation.
5
+ */
6
+ export function useIntersectionObserver(ref, { threshold = 0, root = null, rootMargin = '0%', freezeOnceVisible = false, } = {}) {
7
+ const [entry, setEntry] = useState(null);
8
+ const frozen = entry?.isIntersecting && freezeOnceVisible;
9
+ useEffect(() => {
10
+ const el = ref.current;
11
+ if (!el || frozen || typeof IntersectionObserver === 'undefined')
12
+ return;
13
+ const observer = new IntersectionObserver(([e]) => setEntry(e ?? null), {
14
+ threshold,
15
+ root,
16
+ rootMargin,
17
+ });
18
+ observer.observe(el);
19
+ return () => observer.disconnect();
20
+ }, [ref, threshold, root, rootMargin, frozen]);
21
+ return entry;
22
+ }
@@ -0,0 +1,3 @@
1
+ import { useEffect } from 'react';
2
+ export declare const useIsomorphicLayoutEffect: typeof useEffect;
3
+ //# sourceMappingURL=use-isomorphic-layout-effect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-isomorphic-layout-effect.d.ts","sourceRoot":"","sources":["../../src/hooks/use-isomorphic-layout-effect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAmB,MAAM,OAAO,CAAA;AAIlD,eAAO,MAAM,yBAAyB,kBACuB,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { useEffect, useLayoutEffect } from 'react';
2
+ // useLayoutEffect warns in SSR. This hook silences that by falling back to
3
+ // useEffect on the server where window is not available.
4
+ export const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Returns a ref always pointing to the latest value.
3
+ * Breaks stale closures in event listeners, intervals, and callbacks
4
+ * without requiring them to re-subscribe on every render.
5
+ */
6
+ export declare function useLatest<T>(value: T): import("react").RefObject<T>;
7
+ //# sourceMappingURL=use-latest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-latest.d.ts","sourceRoot":"","sources":["../../src/hooks/use-latest.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,gCAIpC"}
@@ -0,0 +1,11 @@
1
+ import { useRef } from 'react';
2
+ /**
3
+ * Returns a ref always pointing to the latest value.
4
+ * Breaks stale closures in event listeners, intervals, and callbacks
5
+ * without requiring them to re-subscribe on every render.
6
+ */
7
+ export function useLatest(value) {
8
+ const ref = useRef(value);
9
+ ref.current = value;
10
+ return ref;
11
+ }
@@ -0,0 +1,2 @@
1
+ export declare function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((prev: T) => T)) => void, () => void];
2
+ //# sourceMappingURL=use-local-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-local-storage.d.ts","sourceRoot":"","sources":["../../src/hooks/use-local-storage.ts"],"names":[],"mappings":"AAYA,wBAAgB,eAAe,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,CAAC,GACd,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,MAAM,IAAI,CAAC,CAmCxD"}
@@ -0,0 +1,37 @@
1
+ import { useCallback, useRef, useState } from 'react';
2
+ function readFromStorage(key, fallback) {
3
+ if (typeof window === 'undefined')
4
+ return fallback;
5
+ try {
6
+ const item = window.localStorage.getItem(key);
7
+ return item !== null ? JSON.parse(item) : fallback;
8
+ }
9
+ catch {
10
+ return fallback;
11
+ }
12
+ }
13
+ export function useLocalStorage(key, initialValue) {
14
+ const initialValueRef = useRef(initialValue);
15
+ const [storedValue, setStoredValue] = useState(() => readFromStorage(key, initialValueRef.current));
16
+ const setValue = useCallback((value) => {
17
+ setStoredValue((prev) => {
18
+ const next = value instanceof Function ? value(prev) : value;
19
+ if (typeof window !== 'undefined') {
20
+ try {
21
+ window.localStorage.setItem(key, JSON.stringify(next));
22
+ }
23
+ catch (error) {
24
+ console.error(`useLocalStorage: failed to write key "${key}"`, error);
25
+ }
26
+ }
27
+ return next;
28
+ });
29
+ }, [key]);
30
+ const removeValue = useCallback(() => {
31
+ if (typeof window !== 'undefined') {
32
+ window.localStorage.removeItem(key);
33
+ }
34
+ setStoredValue(initialValueRef.current);
35
+ }, [key]);
36
+ return [storedValue, setValue, removeValue];
37
+ }
@@ -0,0 +1,2 @@
1
+ export declare function useMediaQuery(query: string): boolean;
2
+ //# sourceMappingURL=use-media-query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-media-query.d.ts","sourceRoot":"","sources":["../../src/hooks/use-media-query.ts"],"names":[],"mappings":"AAEA,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAkBpD"}
@@ -0,0 +1,18 @@
1
+ import { useEffect, useState } from 'react';
2
+ export function useMediaQuery(query) {
3
+ const [matches, setMatches] = useState(() => {
4
+ if (typeof window === 'undefined')
5
+ return false;
6
+ return window.matchMedia(query).matches;
7
+ });
8
+ useEffect(() => {
9
+ if (typeof window === 'undefined')
10
+ return;
11
+ const mql = window.matchMedia(query);
12
+ setMatches(mql.matches);
13
+ const handler = (event) => setMatches(event.matches);
14
+ mql.addEventListener('change', handler);
15
+ return () => mql.removeEventListener('change', handler);
16
+ }, [query]);
17
+ return matches;
18
+ }
@@ -0,0 +1,10 @@
1
+ interface NetworkStatus {
2
+ online: boolean;
3
+ }
4
+ /**
5
+ * Tracks browser online/offline status.
6
+ * SSR-safe: defaults to `true` on the server.
7
+ */
8
+ export declare function useNetworkStatus(): NetworkStatus;
9
+ export {};
10
+ //# sourceMappingURL=use-network-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-network-status.d.ts","sourceRoot":"","sources":["../../src/hooks/use-network-status.ts"],"names":[],"mappings":"AAEA,UAAU,aAAa;IACrB,MAAM,EAAE,OAAO,CAAA;CAChB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAmBhD"}
@@ -0,0 +1,19 @@
1
+ import { useEffect, useState } from 'react';
2
+ /**
3
+ * Tracks browser online/offline status.
4
+ * SSR-safe: defaults to `true` on the server.
5
+ */
6
+ export function useNetworkStatus() {
7
+ const [online, setOnline] = useState(typeof navigator !== 'undefined' ? navigator.onLine : true);
8
+ useEffect(() => {
9
+ const handleOnline = () => setOnline(true);
10
+ const handleOffline = () => setOnline(false);
11
+ window.addEventListener('online', handleOnline);
12
+ window.addEventListener('offline', handleOffline);
13
+ return () => {
14
+ window.removeEventListener('online', handleOnline);
15
+ window.removeEventListener('offline', handleOffline);
16
+ };
17
+ }, []);
18
+ return { online };
19
+ }
@@ -0,0 +1,3 @@
1
+ import { type RefObject } from 'react';
2
+ export declare function useOnClickOutside<T extends HTMLElement>(ref: RefObject<T | null>, handler: (event: MouseEvent | TouchEvent) => void): void;
3
+ //# sourceMappingURL=use-on-click-outside.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-on-click-outside.d.ts","sourceRoot":"","sources":["../../src/hooks/use-on-click-outside.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAa,MAAM,OAAO,CAAA;AAEjD,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,WAAW,EACrD,GAAG,EAAE,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,EACxB,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,KAAK,IAAI,GAChD,IAAI,CAeN"}
@@ -0,0 +1,17 @@
1
+ import { useEffect } from 'react';
2
+ export function useOnClickOutside(ref, handler) {
3
+ useEffect(() => {
4
+ const listener = (event) => {
5
+ const el = ref.current;
6
+ if (!el || el.contains(event.target))
7
+ return;
8
+ handler(event);
9
+ };
10
+ document.addEventListener('mousedown', listener);
11
+ document.addEventListener('touchstart', listener);
12
+ return () => {
13
+ document.removeEventListener('mousedown', listener);
14
+ document.removeEventListener('touchstart', listener);
15
+ };
16
+ }, [ref, handler]);
17
+ }
@@ -0,0 +1,2 @@
1
+ export declare function usePrevious<T>(value: T): T | undefined;
2
+ //# sourceMappingURL=use-previous.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-previous.d.ts","sourceRoot":"","sources":["../../src/hooks/use-previous.ts"],"names":[],"mappings":"AAEA,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAQtD"}
@@ -0,0 +1,8 @@
1
+ import { useEffect, useRef } from 'react';
2
+ export function usePrevious(value) {
3
+ const ref = useRef(undefined);
4
+ useEffect(() => {
5
+ ref.current = value;
6
+ }, [value]);
7
+ return ref.current;
8
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Returns the number of times the component has rendered.
3
+ * Dev-only: logs to console when a label is provided.
4
+ * Use this to spot unexpected re-render loops before reaching for the profiler.
5
+ */
6
+ export declare function useRenderCount(label?: string): number;
7
+ //# sourceMappingURL=use-render-count.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-render-count.d.ts","sourceRoot":"","sources":["../../src/hooks/use-render-count.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAUrD"}
@@ -0,0 +1,15 @@
1
+ import { useRef } from 'react';
2
+ /**
3
+ * Returns the number of times the component has rendered.
4
+ * Dev-only: logs to console when a label is provided.
5
+ * Use this to spot unexpected re-render loops before reaching for the profiler.
6
+ */
7
+ export function useRenderCount(label) {
8
+ const count = useRef(0);
9
+ count.current += 1;
10
+ if (process.env.NODE_ENV !== 'production' && label) {
11
+ // eslint-disable-next-line no-console
12
+ console.log(`[renders] ${label}: ${count.current}`);
13
+ }
14
+ return count.current;
15
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Boolean state with stable toggle, setOn, and setOff callbacks.
3
+ * Returns `[value, toggle, setOn, setOff]`.
4
+ */
5
+ export declare function useToggle(initialValue?: boolean): [boolean, () => void, () => void, () => void];
6
+ //# sourceMappingURL=use-toggle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-toggle.d.ts","sourceRoot":"","sources":["../../src/hooks/use-toggle.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,SAAS,CACvB,YAAY,UAAQ,GACnB,CAAC,OAAO,EAAE,MAAM,IAAI,EAAE,MAAM,IAAI,EAAE,MAAM,IAAI,CAAC,CAQ/C"}
@@ -0,0 +1,12 @@
1
+ import { useCallback, useState } from 'react';
2
+ /**
3
+ * Boolean state with stable toggle, setOn, and setOff callbacks.
4
+ * Returns `[value, toggle, setOn, setOff]`.
5
+ */
6
+ export function useToggle(initialValue = false) {
7
+ const [value, setValue] = useState(initialValue);
8
+ const toggle = useCallback(() => setValue((v) => !v), []);
9
+ const setOn = useCallback(() => setValue(true), []);
10
+ const setOff = useCallback(() => setValue(false), []);
11
+ return [value, toggle, setOn, setOff];
12
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Logs which props changed between renders.
3
+ * Dev-only (no-ops in production).
4
+ * Use before reaching for the React DevTools profiler — shows changed props
5
+ * inline in the console with before/after values.
6
+ */
7
+ export declare function useWhyDidYouRender(name: string, props: Record<string, unknown>): void;
8
+ //# sourceMappingURL=use-why-did-you-render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-why-did-you-render.d.ts","sourceRoot":"","sources":["../../src/hooks/use-why-did-you-render.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CAmCN"}
@@ -0,0 +1,38 @@
1
+ import { useEffect, useRef } from 'react';
2
+ /**
3
+ * Logs which props changed between renders.
4
+ * Dev-only (no-ops in production).
5
+ * Use before reaching for the React DevTools profiler — shows changed props
6
+ * inline in the console with before/after values.
7
+ */
8
+ export function useWhyDidYouRender(name, props) {
9
+ const hasMounted = useRef(false);
10
+ const previousProps = useRef({});
11
+ useEffect(() => {
12
+ if (process.env.NODE_ENV === 'production')
13
+ return;
14
+ // Skip first render — nothing to diff against
15
+ if (!hasMounted.current) {
16
+ hasMounted.current = true;
17
+ previousProps.current = { ...props };
18
+ return;
19
+ }
20
+ const changed = {};
21
+ for (const key of Object.keys(props)) {
22
+ if (previousProps.current[key] !== props[key]) {
23
+ changed[key] = {
24
+ from: previousProps.current[key],
25
+ to: props[key],
26
+ };
27
+ }
28
+ }
29
+ if (Object.keys(changed).length > 0) {
30
+ console.group(`[why-render] ${name}`);
31
+ for (const [key, diff] of Object.entries(changed)) {
32
+ console.log(` ${key}:`, diff);
33
+ }
34
+ console.groupEnd();
35
+ }
36
+ previousProps.current = { ...props };
37
+ });
38
+ }
package/dist/index.d.ts CHANGED
@@ -1,16 +1,18 @@
1
1
  /**
2
2
  * @teo-garcia/react-shared
3
3
  *
4
- * Shared React components, hooks, utilities, and adapters for fullstack web templates.
4
+ * Shared React hooks, utilities, and test helpers for the teo-garcia template portfolio.
5
5
  *
6
- * This package provides framework-agnostic React utilities that work across
7
- * Next.js, React Router, and other React-based frameworks.
6
+ * Exports:
7
+ * - Hooks: useDebounce, useIsomorphicLayoutEffect, useLocalStorage, useMediaQuery, useOnClickOutside, usePrevious
8
+ * - Components: ErrorBoundary
9
+ * - Utils: cn (clsx + tailwind-merge)
10
+ * - Test utilities: createWrapper, renderWithProviders (import from react-shared/test-utils)
8
11
  *
9
12
  * @packageDocumentation
10
13
  */
11
14
  export * from './components/index.js';
12
15
  export * from './hooks/index.js';
13
16
  export * from './utils/index.js';
14
- export * from './adapters/index.js';
15
17
  export * from './types.js';
16
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,cAAc,uBAAuB,CAAA;AAGrC,cAAc,kBAAkB,CAAA;AAGhC,cAAc,kBAAkB,CAAA;AAGhC,cAAc,qBAAqB,CAAA;AAGnC,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -1,20 +1,17 @@
1
1
  /**
2
2
  * @teo-garcia/react-shared
3
3
  *
4
- * Shared React components, hooks, utilities, and adapters for fullstack web templates.
4
+ * Shared React hooks, utilities, and test helpers for the teo-garcia template portfolio.
5
5
  *
6
- * This package provides framework-agnostic React utilities that work across
7
- * Next.js, React Router, and other React-based frameworks.
6
+ * Exports:
7
+ * - Hooks: useDebounce, useIsomorphicLayoutEffect, useLocalStorage, useMediaQuery, useOnClickOutside, usePrevious
8
+ * - Components: ErrorBoundary
9
+ * - Utils: cn (clsx + tailwind-merge)
10
+ * - Test utilities: createWrapper, renderWithProviders (import from react-shared/test-utils)
8
11
  *
9
12
  * @packageDocumentation
10
13
  */
11
- // Export all components
12
14
  export * from './components/index.js';
13
- // Export all hooks
14
15
  export * from './hooks/index.js';
15
- // Export all utilities
16
16
  export * from './utils/index.js';
17
- // Export all adapters
18
- export * from './adapters/index.js';
19
- // Export all types
20
17
  export * from './types.js';
@@ -0,0 +1,14 @@
1
+ import { QueryClient } from '@tanstack/react-query';
2
+ import { type RenderOptions, type RenderResult } from '@testing-library/react';
3
+ import { type ReactNode } from 'react';
4
+ export interface WrapperOptions {
5
+ queryClient?: QueryClient;
6
+ }
7
+ export declare function createWrapper(options?: WrapperOptions): ({ children }: {
8
+ children: ReactNode;
9
+ }) => import("react").FunctionComponentElement<import("@tanstack/react-query").QueryClientProviderProps>;
10
+ export interface RenderWithProvidersOptions extends Omit<RenderOptions, 'wrapper'> {
11
+ queryClient?: QueryClient;
12
+ }
13
+ export declare function renderWithProviders(ui: React.ReactElement, options?: RenderWithProvidersOptions): RenderResult;
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test-utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAuB,MAAM,uBAAuB,CAAA;AACxE,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAErD,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B;AAED,wBAAgB,aAAa,CAAC,OAAO,GAAE,cAAmB,IAUhC,cAAc;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,wGAG9D;AAED,MAAM,WAAW,0BACf,SAAQ,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC;IACtC,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B;AAED,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,KAAK,CAAC,YAAY,EACtB,OAAO,GAAE,0BAA+B,GACvC,YAAY,CAMd"}
@@ -0,0 +1,22 @@
1
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
+ import { render, } from '@testing-library/react';
3
+ import { createElement } from 'react';
4
+ export function createWrapper(options = {}) {
5
+ const client = options.queryClient ??
6
+ new QueryClient({
7
+ defaultOptions: {
8
+ queries: { retry: false, gcTime: 0 },
9
+ mutations: { retry: false },
10
+ },
11
+ });
12
+ return function Wrapper({ children }) {
13
+ return createElement(QueryClientProvider, { client }, children);
14
+ };
15
+ }
16
+ export function renderWithProviders(ui, options = {}) {
17
+ const { queryClient, ...renderOptions } = options;
18
+ return render(ui, {
19
+ wrapper: createWrapper({ queryClient }),
20
+ ...renderOptions,
21
+ });
22
+ }