"use client"; import React from 'react'; import { cn } from "../utils/cn"; import { useDynamicTheming } from '../hooks/use-dynamic-theming'; import { useThemeAware } from '../hooks/use-theme-aware'; interface DynamicSkeletonProps { /** * Type of skeleton animation */ animation?: 'pulse' | 'shimmer' | 'wave' | 'static'; /** * Skeleton variant based on content type */ variant?: 'text' | 'avatar' | 'card' | 'button' | 'image' | 'custom'; /** * Size preset for common elements */ size?: 'sm' | 'md' | 'lg' | 'xl'; /** * Custom dimensions */ width?: string | number; height?: string | number; /** * Number of lines for text skeleton */ lines?: number; /** * Platform-aware styling */ platformAware?: boolean; /** * Accessibility enhancements */ includeAriaLabel?: boolean; /** * Custom CSS classes */ className?: string; /** * Child elements (for container skeletons) */ children?: React.ReactNode; } const sizePresets = { sm: { width: '4rem', height: '1rem' }, md: { width: '8rem', height: '1.5rem' }, lg: { width: '12rem', height: '2rem' }, xl: { width: '16rem', height: '2.5rem' } }; const variantStyles = { text: 'rounded', avatar: 'rounded-full aspect-square', card: 'rounded-lg', button: 'rounded-md', image: 'rounded-lg aspect-video', custom: '' }; export function DynamicSkeleton({ animation = 'shimmer', variant = 'text', size = 'md', width, height, lines = 1, platformAware = true, includeAriaLabel = true, className, children }: DynamicSkeletonProps) { const { platform, isDark, accentColor } = useThemeAware(); // Calculate dimensions const dimensions = React.useMemo(() => { if (width || height) { return { width: typeof width === 'number' ? `${width}px` : width, height: typeof height === 'number' ? `${height}px` : height }; } if (variant === 'avatar') { const avatarSize = sizePresets[size].height; return { width: avatarSize, height: avatarSize }; } return sizePresets[size]; }, [width, height, size, variant]); // Platform-specific skeleton colors const platformStyles = React.useMemo(() => { if (!platformAware) return {}; const baseOpacity = isDark ? 0.1 : 0.15; const accentOpacity = isDark ? 0.05 : 0.08; return { '--skeleton-base': `color-mix(in srgb, var(--color-bg-skeleton) ${baseOpacity * 100}%, transparent)`, '--skeleton-highlight': `color-mix(in srgb, ${accentColor} ${accentOpacity * 100}%, var(--color-bg-skeleton))`, '--skeleton-accent': `color-mix(in srgb, ${accentColor} 10%, transparent)` } as React.CSSProperties; }, [platformAware, isDark, accentColor]); // Animation classes const animationClasses = { pulse: 'animate-pulse', shimmer: 'ods-loading-dynamic', wave: 'skeleton-wave', static: '' }; // Base skeleton classes const baseClasses = cn( 'bg-ods-skeleton', variantStyles[variant], animationClasses[animation], platformAware && `skeleton-platform-${platform}`, className ); // For text skeletons with multiple lines if (variant === 'text' && lines > 1) { return (
{Array.from({ length: lines }, (_, index) => (
))}
); } // Single skeleton element return (
{children}
); } /** * Pre-configured skeleton for common UI patterns */ export const SkeletonPresets = { /** * Card skeleton with header, content, and actions */ Card: ({ showActions = true, showImage = false }: { showActions?: boolean; showImage?: boolean }) => (
{showImage && ( )}
{showActions && (
)}
), /** * User profile skeleton */ Profile: () => (
), /** * Navigation skeleton */ Navigation: ({ items = 5 }: { items?: number }) => ( ), /** * Table skeleton */ Table: ({ rows = 5, columns = 4 }: { rows?: number; columns?: number }) => (
{/* Header */}
{Array.from({ length: columns }, (_, index) => ( ))}
{/* Rows */} {Array.from({ length: rows }, (_, rowIndex) => (
{Array.from({ length: columns }, (_, colIndex) => ( ))}
))}
), /** * Vendor grid skeleton (specific to the app) */ VendorGrid: ({ items = 6 }: { items?: number }) => (
{Array.from({ length: items }, (_, index) => (
))}
), /** * Blog post skeleton */ BlogPost: () => (
{Array.from({ length: 4 }, (_, index) => ( ))}
) }; /** * Platform-aware loading container that shows different skeletons based on platform */ export function PlatformSkeletonContainer({ children, isLoading, skeletonType = 'Card', skeletonProps = {} }: { children: React.ReactNode; isLoading: boolean; skeletonType?: keyof typeof SkeletonPresets; skeletonProps?: any; }) { const { platform } = useThemeAware(); if (!isLoading) { return <>{children}; } const SkeletonComponent = SkeletonPresets[skeletonType]; return (
); } /** * Enhanced skeleton with progressive enhancement */ export function ProgressiveSkeleton({ stages = ['basic', 'detailed', 'interactive'], currentStage = 0, children, ...props }: DynamicSkeletonProps & { stages?: string[]; currentStage?: number; }) { const { platform } = useThemeAware(); // Show more detailed skeleton as loading progresses const stageConfig = { 0: { animation: 'pulse' as const, variant: 'text' as const }, 1: { animation: 'shimmer' as const, variant: 'card' as const }, 2: { animation: 'wave' as const, variant: 'custom' as const } }; const config = stageConfig[currentStage as keyof typeof stageConfig] || stageConfig[0]; return ( {children} ); }