@beyondcorp/beyond-ui 1.2.67 → 1.2.69
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/dist/components/Image/Image.js +7 -1
- package/dist/components/Image/Image.js.map +1 -1
- package/dist/hooks/useIntersectionObserver.d.ts +9 -0
- package/dist/hooks/useIntersectionObserver.js +39 -0
- package/dist/hooks/useIntersectionObserver.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -2,12 +2,18 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Skeleton } from '../Skeleton/Skeleton.js';
|
|
4
4
|
import { Spinner } from '../Spinner/Spinner.js';
|
|
5
|
+
import { useIntersectionObserver } from '../../hooks/useIntersectionObserver.js';
|
|
5
6
|
|
|
6
7
|
const DEFAULT_PLACEHOLDER = "https://res.cloudinary.com/dmpposta9/image/upload/v1759505259/beyond/beyond%20ui/beyond-ui-logo-part-with-bg_1_wydfry.png";
|
|
7
8
|
const Image = ({ src, alt, className = "", fallbackSrc = DEFAULT_PLACEHOLDER, skeletonClassName = "w-full h-full", ...imgProps }) => {
|
|
8
9
|
const [loaded, setLoaded] = useState(false);
|
|
9
10
|
const [error, setError] = useState(false);
|
|
10
|
-
|
|
11
|
+
const [containerRef, inView] = useIntersectionObserver({ threshold: 0.1, freezeOnceVisible: true });
|
|
12
|
+
// Only load image if inView (or fallback if IntersectionObserver unsupported)
|
|
13
|
+
const shouldLoad = inView;
|
|
14
|
+
return (jsxs("div", { ref: containerRef, className: `relative ${className}`, children: [!loaded && !error && (jsxs("div", { className: `absolute inset-0 flex items-center justify-center ${skeletonClassName}`, children: [jsx(Skeleton, { className: "w-full h-full absolute inset-0" }), jsx(Spinner, { className: "relative z-10 w-8 h-8 text-primary-500" })] })), shouldLoad && (jsx("img", { src: error ? fallbackSrc : src, alt: alt,
|
|
15
|
+
// loading="lazy"
|
|
16
|
+
style: loaded ? {} : { display: "none" }, onLoad: () => setLoaded(true), onError: () => setError(true), draggable: imgProps.draggable ?? false, ...imgProps, className: `absolute inset-0 w-full h-full object-cover transition-transform duration-300 ${loaded ? "" : "hidden"}` }))] }));
|
|
11
17
|
};
|
|
12
18
|
|
|
13
19
|
export { Image };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Image.js","sources":["../../../src/components/Image/Image.tsx"],"sourcesContent":["import React, { useState } from \"react\";\r\nimport { Skeleton } from \"../Skeleton\";\r\nimport { Spinner } from \"../Spinner\";\r\n\r\nexport interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {\r\n src: string;\r\n alt: string;\r\n className?: string;\r\n fallbackSrc?: string;\r\n skeletonClassName?: string;\r\n}\r\n\r\nconst DEFAULT_PLACEHOLDER =\r\n \"https://res.cloudinary.com/dmpposta9/image/upload/v1759505259/beyond/beyond%20ui/beyond-ui-logo-part-with-bg_1_wydfry.png\";\r\nexport const Image: React.FC<ImageProps> = ({\r\n src,\r\n alt,\r\n className = \"\",\r\n fallbackSrc = DEFAULT_PLACEHOLDER,\r\n skeletonClassName = \"w-full h-full\",\r\n ...imgProps\r\n}) => {\r\n const [loaded, setLoaded] = useState(false);\r\n const [error, setError] = useState(false);\r\n\r\n return (\r\n <div className={`relative ${className}`}>\r\n {!loaded && !error && (\r\n <div className={`absolute inset-0 flex items-center justify-center ${skeletonClassName}`}>\r\n <Skeleton className=\"w-full h-full absolute inset-0\" />\r\n <Spinner className=\"relative z-10 w-8 h-8 text-primary-500\" />\r\n </div>\r\n )}\r\n <img\r\n
|
|
1
|
+
{"version":3,"file":"Image.js","sources":["../../../src/components/Image/Image.tsx"],"sourcesContent":["import React, { useState } from \"react\";\r\nimport { Skeleton } from \"../Skeleton\";\r\nimport { Spinner } from \"../Spinner\";\r\nimport { useIntersectionObserver } from \"../../hooks/useIntersectionObserver\";\r\n\r\nexport interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {\r\n src: string;\r\n alt: string;\r\n className?: string;\r\n fallbackSrc?: string;\r\n skeletonClassName?: string;\r\n}\r\n\r\nconst DEFAULT_PLACEHOLDER =\r\n \"https://res.cloudinary.com/dmpposta9/image/upload/v1759505259/beyond/beyond%20ui/beyond-ui-logo-part-with-bg_1_wydfry.png\";\r\nexport const Image: React.FC<ImageProps> = ({\r\n src,\r\n alt,\r\n className = \"\",\r\n fallbackSrc = DEFAULT_PLACEHOLDER,\r\n skeletonClassName = \"w-full h-full\",\r\n ...imgProps\r\n}) => {\r\n const [loaded, setLoaded] = useState(false);\r\n const [error, setError] = useState(false);\r\n const [containerRef, inView] = useIntersectionObserver<HTMLDivElement>({ threshold: 0.1, freezeOnceVisible: true });\r\n\r\n // Only load image if inView (or fallback if IntersectionObserver unsupported)\r\n const shouldLoad = inView;\r\n\r\n return (\r\n <div ref={containerRef} className={`relative ${className}`}>\r\n {!loaded && !error && (\r\n <div className={`absolute inset-0 flex items-center justify-center ${skeletonClassName}`}>\r\n <Skeleton className=\"w-full h-full absolute inset-0\" />\r\n <Spinner className=\"relative z-10 w-8 h-8 text-primary-500\" />\r\n </div>\r\n )}\r\n {shouldLoad && (\r\n <img\r\n src={error ? fallbackSrc : src}\r\n alt={alt}\r\n // loading=\"lazy\"\r\n style={loaded ? {} : { display: \"none\" }}\r\n onLoad={() => setLoaded(true)}\r\n onError={() => setError(true)}\r\n draggable={imgProps.draggable ?? false}\r\n {...imgProps}\r\n className={`absolute inset-0 w-full h-full object-cover transition-transform duration-300 ${loaded ? \"\" : \"hidden\"}`}\r\n />\r\n )}\r\n </div>\r\n );\r\n};"],"names":["_jsxs","_jsx"],"mappings":";;;;;;AAaA,MAAM,mBAAmB,GACvB,2HAA2H;AACtH,MAAM,KAAK,GAAyB,CAAC,EAC1C,GAAG,EACH,GAAG,EACH,SAAS,GAAG,EAAE,EACd,WAAW,GAAG,mBAAmB,EACjC,iBAAiB,GAAG,eAAe,EACnC,GAAG,QAAQ,EACZ,KAAI;IACH,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;IAC3C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;AACzC,IAAA,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,GAAG,uBAAuB,CAAiB,EAAE,SAAS,EAAE,GAAG,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC;;IAGnH,MAAM,UAAU,GAAG,MAAM;AAEzB,IAAA,QACEA,IAAA,CAAA,KAAA,EAAA,EAAK,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,CAAA,SAAA,EAAY,SAAS,CAAA,CAAE,EAAA,QAAA,EAAA,CACvD,CAAC,MAAM,IAAI,CAAC,KAAK,KAChBA,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,CAAA,kDAAA,EAAqD,iBAAiB,CAAA,CAAE,EAAA,QAAA,EAAA,CACtFC,GAAA,CAAC,QAAQ,IAAC,SAAS,EAAC,gCAAgC,EAAA,CAAG,EACvDA,GAAA,CAAC,OAAO,EAAA,EAAC,SAAS,EAAC,wCAAwC,EAAA,CAAG,CAAA,EAAA,CAC1D,CACP,EACA,UAAU,KACTA,GAAA,CAAA,KAAA,EAAA,EACE,GAAG,EAAE,KAAK,GAAG,WAAW,GAAG,GAAG,EAC9B,GAAG,EAAE,GAAG;;AAER,gBAAA,KAAK,EAAE,MAAM,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,EACxC,MAAM,EAAE,MAAM,SAAS,CAAC,IAAI,CAAC,EAC7B,OAAO,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,EAC7B,SAAS,EAAE,QAAQ,CAAC,SAAS,IAAI,KAAK,EAAA,GAClC,QAAQ,EACZ,SAAS,EAAE,CAAA,8EAAA,EAAiF,MAAM,GAAG,EAAE,GAAG,QAAQ,CAAA,CAAE,EAAA,CACpH,CACH,CAAA,EAAA,CACG;AAEV;;;;"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface IntersectionOptions extends IntersectionObserverInit {
|
|
2
|
+
freezeOnceVisible?: boolean;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* useIntersectionObserver
|
|
6
|
+
* Returns true if the element is in the viewport.
|
|
7
|
+
* Usage: const [ref, inView] = useIntersectionObserver(options);
|
|
8
|
+
*/
|
|
9
|
+
export declare function useIntersectionObserver<T extends HTMLElement = HTMLElement>(options?: IntersectionOptions): [React.RefObject<T>, boolean];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useIntersectionObserver
|
|
5
|
+
* Returns true if the element is in the viewport.
|
|
6
|
+
* Usage: const [ref, inView] = useIntersectionObserver(options);
|
|
7
|
+
*/
|
|
8
|
+
function useIntersectionObserver(options) {
|
|
9
|
+
const [inView, setInView] = useState(false);
|
|
10
|
+
const ref = useState(() => ({ current: null }))[0];
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const node = ref.current;
|
|
13
|
+
if (!node || typeof window === "undefined" || !("IntersectionObserver" in window)) {
|
|
14
|
+
setInView(true); // Fallback: always load if no observer
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
let frozen = false;
|
|
18
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
19
|
+
if (entry.isIntersecting) {
|
|
20
|
+
setInView(true);
|
|
21
|
+
if (options?.freezeOnceVisible) {
|
|
22
|
+
frozen = true;
|
|
23
|
+
observer.disconnect();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else if (!frozen) {
|
|
27
|
+
setInView(false);
|
|
28
|
+
}
|
|
29
|
+
}, options);
|
|
30
|
+
observer.observe(node);
|
|
31
|
+
return () => {
|
|
32
|
+
observer.disconnect();
|
|
33
|
+
};
|
|
34
|
+
}, [ref, options]);
|
|
35
|
+
return [ref, inView];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { useIntersectionObserver };
|
|
39
|
+
//# sourceMappingURL=useIntersectionObserver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useIntersectionObserver.js","sources":["../../src/hooks/useIntersectionObserver.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\r\n\r\nexport interface IntersectionOptions extends IntersectionObserverInit {\r\n freezeOnceVisible?: boolean;\r\n}\r\n\r\n/**\r\n * useIntersectionObserver\r\n * Returns true if the element is in the viewport.\r\n * Usage: const [ref, inView] = useIntersectionObserver(options);\r\n */\r\nexport function useIntersectionObserver<T extends HTMLElement = HTMLElement>(\r\n options?: IntersectionOptions\r\n): [React.RefObject<T>, boolean] {\r\n const [inView, setInView] = useState(false);\r\n const ref = useState<React.RefObject<T>>(() => ({ current: null }))[0];\r\n\r\n useEffect(() => {\r\n const node = ref.current;\r\n if (!node || typeof window === \"undefined\" || !(\"IntersectionObserver\" in window)) {\r\n setInView(true); // Fallback: always load if no observer\r\n return;\r\n }\r\n\r\n let frozen = false;\r\n const observer = new IntersectionObserver(\r\n ([entry]) => {\r\n if (entry.isIntersecting) {\r\n setInView(true);\r\n if (options?.freezeOnceVisible) {\r\n frozen = true;\r\n observer.disconnect();\r\n }\r\n } else if (!frozen) {\r\n setInView(false);\r\n }\r\n },\r\n options\r\n );\r\n\r\n observer.observe(node);\r\n\r\n return () => {\r\n observer.disconnect();\r\n };\r\n }, [ref, options]);\r\n\r\n return [ref, inView];\r\n}"],"names":[],"mappings":";;AAMA;;;;AAIG;AACG,SAAU,uBAAuB,CACrC,OAA6B,EAAA;IAE7B,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;AAC3C,IAAA,MAAM,GAAG,GAAG,QAAQ,CAAqB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAEtE,SAAS,CAAC,MAAK;AACb,QAAA,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO;AACxB,QAAA,IAAI,CAAC,IAAI,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,EAAE,sBAAsB,IAAI,MAAM,CAAC,EAAE;AACjF,YAAA,SAAS,CAAC,IAAI,CAAC,CAAC;YAChB;QACF;QAEA,IAAI,MAAM,GAAG,KAAK;QAClB,MAAM,QAAQ,GAAG,IAAI,oBAAoB,CACvC,CAAC,CAAC,KAAK,CAAC,KAAI;AACV,YAAA,IAAI,KAAK,CAAC,cAAc,EAAE;gBACxB,SAAS,CAAC,IAAI,CAAC;AACf,gBAAA,IAAI,OAAO,EAAE,iBAAiB,EAAE;oBAC9B,MAAM,GAAG,IAAI;oBACb,QAAQ,CAAC,UAAU,EAAE;gBACvB;YACF;iBAAO,IAAI,CAAC,MAAM,EAAE;gBAClB,SAAS,CAAC,KAAK,CAAC;YAClB;QACF,CAAC,EACD,OAAO,CACR;AAED,QAAA,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;AAEtB,QAAA,OAAO,MAAK;YACV,QAAQ,CAAC,UAAU,EAAE;AACvB,QAAA,CAAC;AACH,IAAA,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAElB,IAAA,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC;AACtB;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ export * from './hooks/useDebounce';
|
|
|
36
36
|
export * from './hooks/useLocalStorage';
|
|
37
37
|
export * from './hooks/useToggle';
|
|
38
38
|
export * from './hooks/useBreakpoint';
|
|
39
|
+
export * from './hooks/useIntersectionObserver';
|
|
39
40
|
export * from './utils/cn';
|
|
40
41
|
export * from './theme/default';
|
|
41
42
|
export * from './contexts/AuthContext';
|
package/dist/index.js
CHANGED
|
@@ -64,6 +64,7 @@ export { useDebounce } from './hooks/useDebounce.js';
|
|
|
64
64
|
export { useLocalStorage } from './hooks/useLocalStorage.js';
|
|
65
65
|
export { useToggle } from './hooks/useToggle.js';
|
|
66
66
|
export { useBreakpoint } from './hooks/useBreakpoint.js';
|
|
67
|
+
export { useIntersectionObserver } from './hooks/useIntersectionObserver.js';
|
|
67
68
|
export { cn } from './utils/cn.js';
|
|
68
69
|
export { defaultTheme } from './theme/default.js';
|
|
69
70
|
export { AuthProvider, useAuth } from './contexts/AuthContext.js';
|