'use client';
import * as React from 'react';
import { Suspense, type ReactNode, type ComponentType } from 'react';
import { cn } from '@djangocfg/ui-core/lib';
import { useAppT } from '@djangocfg/i18n';
// ============================================================================
// Loading Fallback Components
// ============================================================================
export interface LoadingFallbackProps {
/** Minimum height of the loading container */
minHeight?: string | number;
/** Show loading text */
showText?: boolean;
/** Custom loading text */
text?: string;
/** Additional CSS classes */
className?: string;
}
/**
* Spinner - Simple spinning loader
*/
export function Spinner({ className }: { className?: string }) {
const t = useAppT();
const loadingLabel = t('ui.states.loading');
return (
);
}
/**
* LoadingFallback - Default loading state for lazy components
*/
export function LoadingFallback({
minHeight = 200,
showText = true,
text,
className,
}: LoadingFallbackProps) {
const t = useAppT();
const loadingText = text ?? t('ui.form.loading');
const height = typeof minHeight === 'number' ? `${minHeight}px` : minHeight;
return (
{showText && (
{loadingText}
)}
);
}
/**
* CardLoadingFallback - Loading state styled as a card
*/
export function CardLoadingFallback({
title,
description,
minHeight = 200,
className,
}: {
title?: string;
description?: string;
minHeight?: string | number;
className?: string;
}) {
const t = useAppT();
const cardTitle = title ?? t('ui.states.loading');
const height = typeof minHeight === 'number' ? `${minHeight}px` : minHeight;
return (
{cardTitle}
{description && (
{description}
)}
);
}
/**
* MapLoadingFallback - Loading state for map components
*/
export function MapLoadingFallback({
minHeight = 400,
className,
}: {
minHeight?: string | number;
className?: string;
}) {
const t = useAppT();
const loadingText = t('ui.form.loading');
const height = typeof minHeight === 'number' ? `${minHeight}px` : minHeight;
return (
);
}
// ============================================================================
// Lazy Wrapper
// ============================================================================
export interface LazyWrapperProps {
children: ReactNode;
/** Custom fallback component */
fallback?: ReactNode;
/** Use card-style fallback */
card?: boolean;
/** Card title (when card=true) */
cardTitle?: string;
/** Card description (when card=true) */
cardDescription?: string;
/** Minimum height for fallback */
minHeight?: string | number;
/** Additional CSS classes for fallback */
className?: string;
}
/**
* LazyWrapper - Suspense wrapper with configurable fallbacks
*
* @example
* // Basic usage
*
*
*
*
* @example
* // Card style fallback
*
*
*
*
* @example
* // Custom fallback
* }>
*
*
*/
export function LazyWrapper({
children,
fallback,
card = false,
cardTitle,
cardDescription,
minHeight = 200,
className,
}: LazyWrapperProps) {
const defaultFallback = card ? (
) : (
);
return {children};
}
// ============================================================================
// Lazy Component Factory
// ============================================================================
export interface CreateLazyComponentOptions {
/** Loading fallback component */
fallback?: ReactNode | ((props: P) => ReactNode);
/** Display name for the component */
displayName?: string;
}
/**
* createLazyComponent - Factory for creating lazy-loaded components
*
* @example
* const LazyMermaid = createLazyComponent(
* () => import('./Mermaid.client'),
* {
* displayName: 'Mermaid',
* fallback: ,
* }
* );
*/
export function createLazyComponent
(
loader: () => Promise<{ default: ComponentType
}>,
options: CreateLazyComponentOptions
= {}
): ComponentType
{
const LazyComponent = React.lazy(loader);
const WrappedComponent = (props: P) => {
const fallback =
typeof options.fallback === 'function'
? options.fallback(props)
: options.fallback ?? ;
return (
);
};
WrappedComponent.displayName = options.displayName ?? 'LazyComponent';
return WrappedComponent;
}