@ai-growth/nextjs
Version:
Seamlessly integrate Sanity CMS with Next.js applications for automated blog routing and rendering
189 lines (188 loc) • 9.34 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.LazyImage = exports.IntersectionWrapper = exports.RouteBasedLazy = exports.LazyErrorBoundary = exports.SuspenseWrapper = exports.LazyLoadComponent = void 0;
exports.withLazyLoading = withLazyLoading;
exports.createLazyComponentWithSuspense = createLazyComponentWithSuspense;
const jsx_runtime_1 = require("react/jsx-runtime");
/**
* @fileoverview Lazy Loading React Components
*
* This module provides React components for implementing lazy loading patterns
* with JSX support, including LazyLoadComponent and error boundaries.
*/
const react_1 = __importStar(require("react"));
const lazy_loading_1 = require("../utils/lazy-loading");
const ErrorBoundary_1 = require("./ErrorBoundary");
/**
* Lazy loading component with intersection observer
*/
const LazyLoadComponent = ({ component: Component, props = {}, fallback = null, errorFallback = null, className, style, intersectionOptions, immediate = false, }) => {
const [elementRef, isIntersecting] = (0, lazy_loading_1.useIntersectionObserver)(intersectionOptions);
const shouldLoad = immediate || isIntersecting;
const { component, loading, error } = (0, lazy_loading_1.useDynamicImport)(() => Promise.resolve({ default: Component }), shouldLoad);
if (error && errorFallback) {
return (0, jsx_runtime_1.jsx)("div", { ref: elementRef, className: className, style: style, children: errorFallback });
}
if (loading || !component) {
return (0, jsx_runtime_1.jsx)("div", { ref: elementRef, className: className, style: style, children: fallback });
}
const LoadedComponent = component;
return ((0, jsx_runtime_1.jsx)("div", { ref: elementRef, className: className, style: style, children: (0, jsx_runtime_1.jsx)(LoadedComponent, { ...props }) }));
};
exports.LazyLoadComponent = LazyLoadComponent;
/**
* Enhanced Suspense wrapper with error boundary
*/
const SuspenseWrapper = ({ children, fallback = null, errorFallback, onError = () => { }, }) => {
return ((0, jsx_runtime_1.jsx)(ErrorBoundary_1.ErrorBoundary, { fallback: errorFallback ? react_1.default.createElement(errorFallback, {
error: new Error('Component error'),
retry: () => window.location.reload()
}) : null, onError: onError, children: (0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: fallback, children: children }) }));
};
exports.SuspenseWrapper = SuspenseWrapper;
/**
* Error boundary specifically for lazy-loaded components
*/
class LazyErrorBoundary extends react_1.Component {
constructor(props) {
super(props);
this.handleRetry = () => {
this.setState({ hasError: false, error: null });
};
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Lazy component loading error:', error, errorInfo);
this.props.onError?.(error);
}
render() {
if (this.state.hasError && this.state.error) {
const { ErrorFallback } = this.props;
return (0, jsx_runtime_1.jsx)(ErrorFallback, { error: this.state.error, retry: this.handleRetry });
}
return this.props.children;
}
}
exports.LazyErrorBoundary = LazyErrorBoundary;
/**
* Create a lazy-loaded higher-order component
*/
function withLazyLoading(loader, options = {}) {
const { fallback = null, errorFallback = null, onError = () => { }, preloadOnHover = false, hoverDelay = 300, } = options;
const LazyComponent = react_1.default.lazy(loader);
return react_1.default.forwardRef((props, ref) => {
const [isHovered, setIsHovered] = (0, react_1.useState)(false);
const [shouldPreload, setShouldPreload] = (0, react_1.useState)(false);
// Handle hover preloading
(0, react_1.useEffect)(() => {
if (preloadOnHover && isHovered && !shouldPreload) {
const timer = setTimeout(() => {
setShouldPreload(true);
}, hoverDelay);
return () => clearTimeout(timer);
}
return () => { };
}, [isHovered, preloadOnHover, hoverDelay, shouldPreload]);
const handleMouseEnter = preloadOnHover ? () => setIsHovered(true) : undefined;
const handleMouseLeave = preloadOnHover ? () => setIsHovered(false) : undefined;
return ((0, jsx_runtime_1.jsx)("div", { onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, style: { display: 'contents' }, ref: ref, children: (0, jsx_runtime_1.jsx)(ErrorBoundary_1.ErrorBoundary, { fallback: errorFallback, onError: onError, children: (0, jsx_runtime_1.jsxs)(react_1.Suspense, { fallback: fallback, children: [shouldPreload && (0, jsx_runtime_1.jsx)(LazyComponent, { ...props, ref: ref }), !shouldPreload && (0, jsx_runtime_1.jsx)(LazyComponent, { ...props, ref: ref })] }) }) }));
});
}
// ============================================================================
// LAZY LOADING UTILITIES
// ============================================================================
/**
* Create lazy component with Suspense wrapper
*/
function createLazyComponentWithSuspense(loader, fallback = null) {
const LazyComponent = (0, react_1.lazy)(loader);
return function SuspenseWrapper(props) {
return ((0, jsx_runtime_1.jsx)(react_1.default.Suspense, { fallback: fallback, children: (0, jsx_runtime_1.jsx)(LazyComponent, { ...props }) }));
};
}
/**
* Route-based lazy component loader
*/
const RouteBasedLazy = ({ routeMap, currentRoute, componentProps = {}, fallback = null, errorFallback, }) => {
const Component = routeMap[currentRoute];
if (!Component) {
if (errorFallback) {
const ErrorComponent = errorFallback;
return ((0, jsx_runtime_1.jsx)(ErrorComponent, { error: new Error(`Route not found: ${currentRoute}`), retry: () => window.location.reload() }));
}
return (0, jsx_runtime_1.jsxs)("div", { children: ["Route not found: ", currentRoute] });
}
const suspenseProps = {
fallback,
};
if (errorFallback) {
suspenseProps.errorFallback = errorFallback;
}
return ((0, jsx_runtime_1.jsx)(exports.SuspenseWrapper, { ...suspenseProps, children: (0, jsx_runtime_1.jsx)(Component, { ...componentProps }) }));
};
exports.RouteBasedLazy = RouteBasedLazy;
/**
* Wrapper component that only renders children when in viewport
*/
const IntersectionWrapper = ({ children, placeholder = null, options, className, style, onIntersect, }) => {
const [elementRef, isIntersecting] = (0, lazy_loading_1.useIntersectionObserver)(options);
react_1.default.useEffect(() => {
if (isIntersecting && onIntersect) {
onIntersect();
}
}, [isIntersecting, onIntersect]);
return ((0, jsx_runtime_1.jsx)("div", { ref: (node) => {
if (elementRef.current !== node) {
elementRef.current = node;
}
}, className: className, style: style, children: isIntersecting ? children : placeholder }));
};
exports.IntersectionWrapper = IntersectionWrapper;
const LazyImage = ({ src, alt, placeholder = 'blur', className, ...props }) => {
const imgRef = react_1.default.useRef(null);
const [isLoaded, setIsLoaded] = react_1.default.useState(false);
react_1.default.useEffect(() => {
const img = imgRef.current;
if (img && img.complete) {
setIsLoaded(true);
}
}, []);
return ((0, jsx_runtime_1.jsxs)("div", { className: "image-container", children: [!isLoaded && placeholder, (0, jsx_runtime_1.jsx)("img", { ref: imgRef, src: src, alt: alt, className: className, loading: "lazy", decoding: "async", style: { display: isLoaded ? 'block' : 'none' }, onLoad: () => setIsLoaded(true), ...props })] }));
};
exports.LazyImage = LazyImage;