@teo-garcia/react-shared 1.0.0 → 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.
- package/README.md +127 -96
- package/dist/components/aspect-ratio/aspect-ratio.d.ts +17 -0
- package/dist/components/aspect-ratio/aspect-ratio.d.ts.map +1 -0
- package/dist/components/aspect-ratio/aspect-ratio.js +14 -0
- package/dist/components/aspect-ratio/index.d.ts +2 -0
- package/dist/components/aspect-ratio/index.d.ts.map +1 -0
- package/dist/components/aspect-ratio/index.js +1 -0
- package/dist/components/debug-json/debug-json.d.ts +14 -0
- package/dist/components/debug-json/debug-json.d.ts.map +1 -0
- package/dist/components/debug-json/debug-json.js +32 -0
- package/dist/components/debug-json/index.d.ts +2 -0
- package/dist/components/debug-json/index.d.ts.map +1 -0
- package/dist/components/debug-json/index.js +1 -0
- package/dist/components/dev-panel/dev-panel.d.ts +30 -0
- package/dist/components/dev-panel/dev-panel.d.ts.map +1 -0
- package/dist/components/dev-panel/dev-panel.js +155 -0
- package/dist/components/dev-panel/index.d.ts +2 -0
- package/dist/components/dev-panel/index.d.ts.map +1 -0
- package/dist/components/dev-panel/index.js +1 -0
- package/dist/components/error-boundary/error-boundary.d.ts +3 -58
- package/dist/components/error-boundary/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary/error-boundary.js +50 -76
- package/dist/components/error-boundary/index.d.ts +1 -0
- package/dist/components/error-boundary/index.d.ts.map +1 -1
- package/dist/components/focus-trap/focus-trap.d.ts +16 -0
- package/dist/components/focus-trap/focus-trap.d.ts.map +1 -0
- package/dist/components/focus-trap/focus-trap.js +57 -0
- package/dist/components/focus-trap/index.d.ts +2 -0
- package/dist/components/focus-trap/index.d.ts.map +1 -0
- package/dist/components/focus-trap/index.js +1 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +9 -0
- package/dist/components/portal/index.d.ts +2 -0
- package/dist/components/portal/index.d.ts.map +1 -0
- package/dist/components/portal/index.js +1 -0
- package/dist/components/portal/portal.d.ts +14 -0
- package/dist/components/portal/portal.d.ts.map +1 -0
- package/dist/components/portal/portal.js +21 -0
- package/dist/components/separator/index.d.ts +2 -0
- package/dist/components/separator/index.d.ts.map +1 -0
- package/dist/components/separator/index.js +1 -0
- package/dist/components/separator/separator.d.ts +11 -0
- package/dist/components/separator/separator.d.ts.map +1 -0
- package/dist/components/separator/separator.js +11 -0
- package/dist/components/skeleton/index.d.ts +2 -0
- package/dist/components/skeleton/index.d.ts.map +1 -0
- package/dist/components/skeleton/index.js +1 -0
- package/dist/components/skeleton/skeleton.d.ts +3 -0
- package/dist/components/skeleton/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton/skeleton.js +5 -0
- package/dist/components/skip-link/index.d.ts +2 -0
- package/dist/components/skip-link/index.d.ts.map +1 -0
- package/dist/components/skip-link/index.js +1 -0
- package/dist/components/skip-link/skip-link.d.ts +13 -0
- package/dist/components/skip-link/skip-link.d.ts.map +1 -0
- package/dist/components/skip-link/skip-link.js +26 -0
- package/dist/components/visually-hidden/index.d.ts +2 -0
- package/dist/components/visually-hidden/index.d.ts.map +1 -0
- package/dist/components/visually-hidden/index.js +1 -0
- package/dist/components/visually-hidden/visually-hidden.d.ts +3 -0
- package/dist/components/visually-hidden/visually-hidden.d.ts.map +1 -0
- package/dist/components/visually-hidden/visually-hidden.js +15 -0
- package/dist/hooks/index.d.ts +9 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +9 -0
- package/dist/hooks/use-copy-to-clipboard.d.ts +7 -0
- package/dist/hooks/use-copy-to-clipboard.d.ts.map +1 -0
- package/dist/hooks/use-copy-to-clipboard.js +23 -0
- package/dist/hooks/use-event-listener.d.ts +4 -0
- package/dist/hooks/use-event-listener.d.ts.map +1 -0
- package/dist/hooks/use-event-listener.js +13 -0
- package/dist/hooks/use-idle.d.ts +7 -0
- package/dist/hooks/use-idle.d.ts.map +1 -0
- package/dist/hooks/use-idle.js +35 -0
- package/dist/hooks/use-intersection-observer.d.ts +15 -0
- package/dist/hooks/use-intersection-observer.d.ts.map +1 -0
- package/dist/hooks/use-intersection-observer.js +22 -0
- package/dist/hooks/use-latest.d.ts +7 -0
- package/dist/hooks/use-latest.d.ts.map +1 -0
- package/dist/hooks/use-latest.js +11 -0
- package/dist/hooks/use-network-status.d.ts +10 -0
- package/dist/hooks/use-network-status.d.ts.map +1 -0
- package/dist/hooks/use-network-status.js +19 -0
- package/dist/hooks/use-render-count.d.ts +7 -0
- package/dist/hooks/use-render-count.d.ts.map +1 -0
- package/dist/hooks/use-render-count.js +15 -0
- package/dist/hooks/use-toggle.d.ts +6 -0
- package/dist/hooks/use-toggle.d.ts.map +1 -0
- package/dist/hooks/use-toggle.js +12 -0
- package/dist/hooks/use-why-did-you-render.d.ts +8 -0
- package/dist/hooks/use-why-did-you-render.d.ts.map +1 -0
- package/dist/hooks/use-why-did-you-render.js +38 -0
- package/dist/types.d.ts +18 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/format-date.d.ts +11 -0
- package/dist/utils/format-date.d.ts.map +1 -0
- package/dist/utils/format-date.js +12 -0
- package/dist/utils/format-number.d.ts +11 -0
- package/dist/utils/format-number.d.ts.map +1 -0
- package/dist/utils/format-number.js +12 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -0
- package/dist/utils/truncate.d.ts +11 -0
- package/dist/utils/truncate.d.ts.map +1 -0
- package/dist/utils/truncate.js +14 -0
- package/package.json +85 -1
|
@@ -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,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,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,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,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/types.d.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode, ErrorInfo } from 'react';
|
|
2
|
+
export interface FallbackProps {
|
|
3
|
+
error: Error;
|
|
4
|
+
resetError: () => void;
|
|
5
|
+
}
|
|
1
6
|
export interface ErrorBoundaryProps {
|
|
2
|
-
children:
|
|
3
|
-
|
|
4
|
-
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
/** A React component receiving `{ error, resetError }` — highest priority fallback */
|
|
9
|
+
FallbackComponent?: ComponentType<FallbackProps>;
|
|
10
|
+
/** Render prop receiving `{ error, resetError }` */
|
|
11
|
+
fallbackRender?: (props: FallbackProps) => ReactNode;
|
|
12
|
+
/** Static element or function `(error) => ReactNode` — lower priority than the two above */
|
|
13
|
+
fallback?: ReactNode | ((error: Error) => ReactNode);
|
|
14
|
+
/** Called after every caught error — use for logging/reporting */
|
|
15
|
+
onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
|
16
|
+
/** Called whenever the boundary resets */
|
|
17
|
+
onReset?: () => void;
|
|
18
|
+
/** When any value in this array changes the boundary resets automatically */
|
|
19
|
+
resetKeys?: Array<unknown>;
|
|
5
20
|
}
|
|
6
21
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEhE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,KAAK,CAAA;IACZ,UAAU,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAA;IACnB,sFAAsF;IACtF,iBAAiB,CAAC,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;IAChD,oDAAoD;IACpD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,SAAS,CAAA;IACpD,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC,CAAA;IACpD,kEAAkE;IAClE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,IAAI,CAAA;IACtD,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;CAC3B"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper around `Intl.DateTimeFormat` with sensible defaults.
|
|
3
|
+
* Covers the 80% case — reach for `date-fns` for complex scheduling logic.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* formatDate(new Date()) // "March 22, 2026"
|
|
7
|
+
* formatDate(new Date(), 'en-US', { dateStyle: 'short' }) // "3/22/26"
|
|
8
|
+
* formatDate('2026-01-01', 'es-ES') // "1 de enero de 2026"
|
|
9
|
+
*/
|
|
10
|
+
export declare function formatDate(date: Date | string | number, locale?: string, options?: Intl.DateTimeFormatOptions): string;
|
|
11
|
+
//# sourceMappingURL=format-date.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-date.d.ts","sourceRoot":"","sources":["../../src/utils/format-date.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,EAC5B,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,GAAE,IAAI,CAAC,qBAA6C,GAC1D,MAAM,CAER"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper around `Intl.DateTimeFormat` with sensible defaults.
|
|
3
|
+
* Covers the 80% case — reach for `date-fns` for complex scheduling logic.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* formatDate(new Date()) // "March 22, 2026"
|
|
7
|
+
* formatDate(new Date(), 'en-US', { dateStyle: 'short' }) // "3/22/26"
|
|
8
|
+
* formatDate('2026-01-01', 'es-ES') // "1 de enero de 2026"
|
|
9
|
+
*/
|
|
10
|
+
export function formatDate(date, locale, options = { dateStyle: 'long' }) {
|
|
11
|
+
return new Intl.DateTimeFormat(locale, options).format(new Date(date));
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper around `Intl.NumberFormat` with sensible defaults.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* formatNumber(1234567) // "1,234,567"
|
|
6
|
+
* formatNumber(0.1234, 'en-US', { style: 'percent' }) // "12%"
|
|
7
|
+
* formatNumber(9900, 'en-US', { style: 'currency', currency: 'USD' }) // "$9,900.00"
|
|
8
|
+
* formatNumber(1200000, 'en-US', { notation: 'compact' }) // "1.2M"
|
|
9
|
+
*/
|
|
10
|
+
export declare function formatNumber(value: number, locale?: string, options?: Intl.NumberFormatOptions): string;
|
|
11
|
+
//# sourceMappingURL=format-number.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-number.d.ts","sourceRoot":"","sources":["../../src/utils/format-number.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,GACjC,MAAM,CAER"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper around `Intl.NumberFormat` with sensible defaults.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* formatNumber(1234567) // "1,234,567"
|
|
6
|
+
* formatNumber(0.1234, 'en-US', { style: 'percent' }) // "12%"
|
|
7
|
+
* formatNumber(9900, 'en-US', { style: 'currency', currency: 'USD' }) // "$9,900.00"
|
|
8
|
+
* formatNumber(1200000, 'en-US', { notation: 'compact' }) // "1.2M"
|
|
9
|
+
*/
|
|
10
|
+
export function formatNumber(value, locale, options) {
|
|
11
|
+
return new Intl.NumberFormat(locale, options).format(value);
|
|
12
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA"}
|
package/dist/utils/index.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncates a string to `maxLength` characters, appending `suffix` if truncated.
|
|
3
|
+
* The total length of the result never exceeds `maxLength`.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* truncate('Hello world', 8) // "Hello…"
|
|
7
|
+
* truncate('Short', 10) // "Short"
|
|
8
|
+
* truncate('Hello world', 8, '...') // "Hello..."
|
|
9
|
+
*/
|
|
10
|
+
export declare function truncate(str: string, maxLength: number, suffix?: string): string;
|
|
11
|
+
//# sourceMappingURL=truncate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"truncate.d.ts","sourceRoot":"","sources":["../../src/utils/truncate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,SAAM,GAAG,MAAM,CAG7E"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncates a string to `maxLength` characters, appending `suffix` if truncated.
|
|
3
|
+
* The total length of the result never exceeds `maxLength`.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* truncate('Hello world', 8) // "Hello…"
|
|
7
|
+
* truncate('Short', 10) // "Short"
|
|
8
|
+
* truncate('Hello world', 8, '...') // "Hello..."
|
|
9
|
+
*/
|
|
10
|
+
export function truncate(str, maxLength, suffix = '…') {
|
|
11
|
+
if (str.length <= maxLength)
|
|
12
|
+
return str;
|
|
13
|
+
return str.slice(0, maxLength - suffix.length) + suffix;
|
|
14
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teo-garcia/react-shared",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Shared React hooks, utilities, and test helpers for the teo-garcia template portfolio",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,6 +18,42 @@
|
|
|
18
18
|
"import": "./dist/components/error-boundary/index.js",
|
|
19
19
|
"types": "./dist/components/error-boundary/index.d.ts"
|
|
20
20
|
},
|
|
21
|
+
"./components/focus-trap": {
|
|
22
|
+
"import": "./dist/components/focus-trap/index.js",
|
|
23
|
+
"types": "./dist/components/focus-trap/index.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./components/portal": {
|
|
26
|
+
"import": "./dist/components/portal/index.js",
|
|
27
|
+
"types": "./dist/components/portal/index.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./components/skeleton": {
|
|
30
|
+
"import": "./dist/components/skeleton/index.js",
|
|
31
|
+
"types": "./dist/components/skeleton/index.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"./components/visually-hidden": {
|
|
34
|
+
"import": "./dist/components/visually-hidden/index.js",
|
|
35
|
+
"types": "./dist/components/visually-hidden/index.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./components/aspect-ratio": {
|
|
38
|
+
"import": "./dist/components/aspect-ratio/index.js",
|
|
39
|
+
"types": "./dist/components/aspect-ratio/index.d.ts"
|
|
40
|
+
},
|
|
41
|
+
"./components/debug-json": {
|
|
42
|
+
"import": "./dist/components/debug-json/index.js",
|
|
43
|
+
"types": "./dist/components/debug-json/index.d.ts"
|
|
44
|
+
},
|
|
45
|
+
"./components/dev-panel": {
|
|
46
|
+
"import": "./dist/components/dev-panel/index.js",
|
|
47
|
+
"types": "./dist/components/dev-panel/index.d.ts"
|
|
48
|
+
},
|
|
49
|
+
"./components/separator": {
|
|
50
|
+
"import": "./dist/components/separator/index.js",
|
|
51
|
+
"types": "./dist/components/separator/index.d.ts"
|
|
52
|
+
},
|
|
53
|
+
"./components/skip-link": {
|
|
54
|
+
"import": "./dist/components/skip-link/index.js",
|
|
55
|
+
"types": "./dist/components/skip-link/index.d.ts"
|
|
56
|
+
},
|
|
21
57
|
"./hooks": {
|
|
22
58
|
"import": "./dist/hooks/index.js",
|
|
23
59
|
"types": "./dist/hooks/index.d.ts"
|
|
@@ -46,6 +82,42 @@
|
|
|
46
82
|
"import": "./dist/hooks/use-previous.js",
|
|
47
83
|
"types": "./dist/hooks/use-previous.d.ts"
|
|
48
84
|
},
|
|
85
|
+
"./hooks/use-copy-to-clipboard": {
|
|
86
|
+
"import": "./dist/hooks/use-copy-to-clipboard.js",
|
|
87
|
+
"types": "./dist/hooks/use-copy-to-clipboard.d.ts"
|
|
88
|
+
},
|
|
89
|
+
"./hooks/use-event-listener": {
|
|
90
|
+
"import": "./dist/hooks/use-event-listener.js",
|
|
91
|
+
"types": "./dist/hooks/use-event-listener.d.ts"
|
|
92
|
+
},
|
|
93
|
+
"./hooks/use-idle": {
|
|
94
|
+
"import": "./dist/hooks/use-idle.js",
|
|
95
|
+
"types": "./dist/hooks/use-idle.d.ts"
|
|
96
|
+
},
|
|
97
|
+
"./hooks/use-intersection-observer": {
|
|
98
|
+
"import": "./dist/hooks/use-intersection-observer.js",
|
|
99
|
+
"types": "./dist/hooks/use-intersection-observer.d.ts"
|
|
100
|
+
},
|
|
101
|
+
"./hooks/use-latest": {
|
|
102
|
+
"import": "./dist/hooks/use-latest.js",
|
|
103
|
+
"types": "./dist/hooks/use-latest.d.ts"
|
|
104
|
+
},
|
|
105
|
+
"./hooks/use-network-status": {
|
|
106
|
+
"import": "./dist/hooks/use-network-status.js",
|
|
107
|
+
"types": "./dist/hooks/use-network-status.d.ts"
|
|
108
|
+
},
|
|
109
|
+
"./hooks/use-render-count": {
|
|
110
|
+
"import": "./dist/hooks/use-render-count.js",
|
|
111
|
+
"types": "./dist/hooks/use-render-count.d.ts"
|
|
112
|
+
},
|
|
113
|
+
"./hooks/use-toggle": {
|
|
114
|
+
"import": "./dist/hooks/use-toggle.js",
|
|
115
|
+
"types": "./dist/hooks/use-toggle.d.ts"
|
|
116
|
+
},
|
|
117
|
+
"./hooks/use-why-did-you-render": {
|
|
118
|
+
"import": "./dist/hooks/use-why-did-you-render.js",
|
|
119
|
+
"types": "./dist/hooks/use-why-did-you-render.d.ts"
|
|
120
|
+
},
|
|
49
121
|
"./utils": {
|
|
50
122
|
"import": "./dist/utils/index.js",
|
|
51
123
|
"types": "./dist/utils/index.d.ts"
|
|
@@ -54,6 +126,18 @@
|
|
|
54
126
|
"import": "./dist/utils/cn.js",
|
|
55
127
|
"types": "./dist/utils/cn.d.ts"
|
|
56
128
|
},
|
|
129
|
+
"./utils/format-date": {
|
|
130
|
+
"import": "./dist/utils/format-date.js",
|
|
131
|
+
"types": "./dist/utils/format-date.d.ts"
|
|
132
|
+
},
|
|
133
|
+
"./utils/format-number": {
|
|
134
|
+
"import": "./dist/utils/format-number.js",
|
|
135
|
+
"types": "./dist/utils/format-number.d.ts"
|
|
136
|
+
},
|
|
137
|
+
"./utils/truncate": {
|
|
138
|
+
"import": "./dist/utils/truncate.js",
|
|
139
|
+
"types": "./dist/utils/truncate.d.ts"
|
|
140
|
+
},
|
|
57
141
|
"./test-utils": {
|
|
58
142
|
"import": "./dist/test-utils/index.js",
|
|
59
143
|
"types": "./dist/test-utils/index.d.ts"
|