UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

502 lines (499 loc) 21.4 kB
'use client'; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import { ChevronLeft, ChevronRight, Pause, Play, Minimize2, Maximize2 } from 'lucide-react'; import { forwardRef, useRef, useState, Children, useCallback, useEffect } from 'react'; import '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import { MotionFramer } from '../../primitives/motion/MotionFramer.js'; import { GlassButton } from '../button/GlassButton.js'; import '../button/GlassFab.js'; import '../button/GlassMagneticButton.js'; import { CardContent } from '../card/index.js'; import { usePredictiveEngine, useInteractionRecorder } from '../advanced/GlassPredictiveEngine.js'; import { useAchievements } from '../advanced/GlassAchievementSystem.js'; import { useBiometricAdaptation } from '../advanced/GlassBiometricAdaptation.js'; import { useEyeTracking } from '../advanced/GlassEyeTracking.js'; import { useSpatialAudio } from '../advanced/GlassSpatialAudio.js'; import { GlassCard } from '../card/GlassCard.js'; /** * GlassCarousel component * A flexible carousel/slider with smooth animations and multiple display modes */ const GlassCarousel = /*#__PURE__*/forwardRef(({ children, items = [], initialIndex = 0, slidesToShow = 1, slidesToScroll = 1, infinite = true, autoPlay = false, autoPlayInterval = 3000, showArrows = true, showDots = true, showIndicators = false, enableSwipe = true, enableKeyboard = true, animationDuration = 500, pauseOnHover = true, height = "400px", gap = "1rem", showFullscreen = false, className, onSlideChange, customPrevArrow, customNextArrow, // Consciousness features predictive = false, preloadContent = false, eyeTracking = false, gazeResponsive = false, adaptive = false, biometricResponsive = false, spatialAudio = false, audioFeedback = false, trackAchievements = false, achievementId, usageContext = "main", ...props }, ref) => { const carouselRef = useRef(null); const autoPlayRef = useRef(); const transitionRef = useRef(); // Consciousness feature hooks - only initialize if features are enabled const predictiveEngine = predictive ? usePredictiveEngine() : null; const eyeTracker = eyeTracking ? useEyeTracking() : null; const biometricAdapter = adaptive ? useBiometricAdaptation() : null; const spatialAudioEngine = spatialAudio ? useSpatialAudio() : null; const achievementTracker = trackAchievements ? useAchievements() : null; const interactionRecorder = predictive || trackAchievements ? useInteractionRecorder(`glass-carousel-${usageContext}`) : null; const [currentIndex, setCurrentIndex] = useState(initialIndex); const [isPlaying, setIsPlaying] = useState(autoPlay); const [isFullscreen, setIsFullscreen] = useState(false); const [isTransitioning, setIsTransitioning] = useState(false); const [dragStart, setDragStart] = useState(null); const [dragEnd, setDragEnd] = useState(null); // Consciousness state const [slideInteractionCounts, setSlideInteractionCounts] = useState({}); const [predictedNextSlide, setPredictedNextSlide] = useState(null); const [adaptiveAutoPlayInterval, setAdaptiveAutoPlayInterval] = useState(autoPlayInterval); const [gazeFocusedSlide, setGazeFocusedSlide] = useState(null); // Determine if consciousness features are enabled const consciousness = predictive || adaptive || eyeTracking || spatialAudio || trackAchievements; // Get carousel items const carouselItems = children ? Children.toArray(children).map((child, index) => ({ id: `item-${index}`, content: child })) : items; const totalItems = carouselItems?.length || 0; const maxIndex = Math.max(0, totalItems - slidesToShow); // Handle slide change const handleSlideChange = useCallback(newIndex => { if (isTransitioning) return; let targetIndex = newIndex; if (infinite) { if (targetIndex < 0) { targetIndex = maxIndex; } else if (targetIndex > maxIndex) { targetIndex = 0; } } else { targetIndex = Math.max(0, Math.min(maxIndex, targetIndex)); } if (targetIndex !== currentIndex) { setIsTransitioning(true); setCurrentIndex(targetIndex); onSlideChange?.(targetIndex); // Reset transition state after animation if (transitionRef.current) { clearTimeout(transitionRef.current); } transitionRef.current = setTimeout(() => { setIsTransitioning(false); }, animationDuration); } }, [currentIndex, maxIndex, infinite, isTransitioning, animationDuration, onSlideChange]); // Enhanced slide change tracking (moved before navigation functions) const enhancedHandleSlideChange = useCallback(newIndex => { // Track slide interaction setSlideInteractionCounts(prev => ({ ...prev, [newIndex]: (prev[newIndex] || 0) + 1 })); // Record interaction for learning if (interactionRecorder) { interactionRecorder.recordClick({ target: { id: `carousel-slide-${newIndex}` } }); } // Track achievements if (achievementTracker && trackAchievements) { achievementTracker.recordAction(achievementId || "carousel_navigation", { slideIndex: newIndex, interactionCount: (slideInteractionCounts[newIndex] || 0) + 1, context: usageContext }); } // Play spatial audio feedback if (spatialAudioEngine && audioFeedback) { spatialAudioEngine.playGlassSound("carousel_slide_change"); } // Call original handler handleSlideChange(newIndex); }, [handleSlideChange, interactionRecorder, achievementTracker, trackAchievements, achievementId, slideInteractionCounts, usageContext, spatialAudioEngine, audioFeedback]); // Navigation functions (updated to use enhanced handler) const goToPrev = useCallback(() => { enhancedHandleSlideChange(currentIndex - slidesToScroll); }, [currentIndex, slidesToScroll, enhancedHandleSlideChange]); const goToNext = useCallback(() => { enhancedHandleSlideChange(currentIndex + slidesToScroll); }, [currentIndex, slidesToScroll, enhancedHandleSlideChange]); const goToSlide = useCallback(index => { enhancedHandleSlideChange(index); }, [enhancedHandleSlideChange]); // Auto-play functionality (with consciousness adaptation) useEffect(() => { if (isPlaying && totalItems > slidesToShow) { autoPlayRef.current = setInterval(() => { goToNext(); }, adaptiveAutoPlayInterval); } else { if (autoPlayRef.current) { clearInterval(autoPlayRef.current); } } return () => { if (autoPlayRef.current) { clearInterval(autoPlayRef.current); } }; }, [isPlaying, totalItems, slidesToShow, adaptiveAutoPlayInterval, goToNext]); // Handle keyboard navigation useEffect(() => { if (!enableKeyboard) return; const handleKeyPress = e => { switch (e.key) { case "ArrowLeft": e.preventDefault(); goToPrev(); break; case "ArrowRight": e.preventDefault(); goToNext(); break; case " ": e.preventDefault(); setIsPlaying(!isPlaying); break; case "Escape": if (isFullscreen) { setIsFullscreen(false); } break; } }; window.addEventListener("keydown", handleKeyPress); return () => window.removeEventListener("keydown", handleKeyPress); }, [enableKeyboard, goToPrev, goToNext, isPlaying, isFullscreen]); // Consciousness effects // Biometric adaptation for autoplay timing useEffect(() => { if (!biometricResponsive || !biometricAdapter) return; const adaptCarousel = () => { biometricAdapter.latestReading; const stressLevel = biometricAdapter.currentStressLevel; // Adapt autoplay speed based on stress level if (stressLevel > 0.7) { setAdaptiveAutoPlayInterval(Math.max(autoPlayInterval * 1.5, 5000)); // Slower when stressed } else if (stressLevel < 0.3) { setAdaptiveAutoPlayInterval(Math.max(autoPlayInterval * 0.8, 2000)); // Faster when relaxed } else { setAdaptiveAutoPlayInterval(autoPlayInterval); } }; adaptCarousel(); const interval = setInterval(adaptCarousel, 5000); return () => clearInterval(interval); }, [biometricResponsive, biometricAdapter, autoPlayInterval]); // Eye tracking for slide attention useEffect(() => { if (!gazeResponsive || !eyeTracker || !carouselRef.current) return; // Eye tracking integration - methods may need to be implemented // eyeTracker.onGazeEnter(carouselRef.current, handleSlideGaze); return () => { if (carouselRef.current) ; }; }, [gazeResponsive, eyeTracker, spatialAudioEngine, audioFeedback]); // Predictive slide navigation useEffect(() => { if (!predictive || !predictiveEngine) return; const updatePredictions = () => { const predictions = predictiveEngine.predictions; const slidePrediction = predictions.find(p => p.type === "navigate" && p.metadata?.carouselContext === usageContext); if (slidePrediction && slidePrediction.confidence > 0.8) { setPredictedNextSlide(slidePrediction.metadata.slideIndex); // Preload predicted slide content if enabled if (preloadContent) { console.log(`Preloading slide content: ${slidePrediction.metadata.slideIndex}`); } } else { setPredictedNextSlide(null); } }; const interval = setInterval(updatePredictions, 2000); updatePredictions(); return () => clearInterval(interval); }, [predictive, predictiveEngine, usageContext, preloadContent]); // Handle mouse/touch events for swipe const handleMouseDown = useCallback(e => { if (!enableSwipe) return; setDragStart(e.clientX); }, [enableSwipe]); const handleMouseMove = useCallback(e => { if (!enableSwipe || dragStart === null) return; setDragEnd(e.clientX); }, [enableSwipe, dragStart]); const handleMouseUp = useCallback(() => { if (!enableSwipe || dragStart === null || dragEnd === null) return; const diff = dragStart - dragEnd; const threshold = 50; if (Math.abs(diff) > threshold) { if (diff > 0) { goToNext(); } else { goToPrev(); } } setDragStart(null); setDragEnd(null); }, [enableSwipe, dragStart, dragEnd, goToNext, goToPrev]); // Handle hover for pause on hover const handleMouseEnter = useCallback(() => { if (pauseOnHover && isPlaying) { setIsPlaying(false); } }, [pauseOnHover, isPlaying]); const handleMouseLeave = useCallback(() => { if (pauseOnHover && autoPlay) { setIsPlaying(true); } }, [pauseOnHover, autoPlay]); // Calculate transform for slides const getTransform = () => { const slideWidth = 100 / slidesToShow; return `translateX(-${currentIndex * slideWidth}%)`; }; // Check if navigation is needed const needsNavigation = totalItems > slidesToShow; if (totalItems === 0) { return jsx(GlassCard, { "data-glass-component": true, className: cn("p-8", className), children: jsx("div", { className: 'text-center text-primary/60', children: "No items to display" }) }); } return jsx(MotionFramer, { preset: "fadeIn", className: "glass-w-full", children: jsx(GlassCard, { ref: ref, elevation: "level2", intensity: "medium", depth: 2, tint: "neutral", border: "subtle", animation: "none", performanceMode: "medium", className: cn("overflow-hidden relative", isFullscreen && "fixed inset-0 z-50 rounded-none", className), ...props, children: jsxs(CardContent, { className: "glass-p-0", children: [jsxs("div", { ref: carouselRef, className: cn("relative overflow-hidden", eyeTracking && "consciousness-eye-trackable", predictive && "consciousness-predictive", adaptive && "consciousness-adaptive"), style: { height: typeof height === "number" ? `${height}px` : height }, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, "data-glass-carousel": "true", "data-carousel-autoplay": String(!!autoPlay), "data-consciousness-component": "carousel", "data-consciousness-active": String(!!consciousness), "data-eye-tracking": String(!!eyeTracking), "data-predictive": String(!!predictive), "data-adaptive": String(!!adaptive), "data-spatial-audio": String(!!spatialAudio), children: [jsx("div", { className: cn("flex h-full transition-transform duration-500 ease-in-out", adaptive && adaptiveAutoPlayInterval !== autoPlayInterval && "consciousness-adaptive-timing", predictive && predictedNextSlide !== null && "consciousness-predictive-active"), style: { transform: getTransform(), gap: typeof gap === "number" ? `${gap}px` : gap, transitionDuration: adaptive ? `${Math.max(300, adaptiveAutoPlayInterval / 10)}ms` : "500ms" }, "data-consciousness-slides-container": "true", "data-predicted-slide": predictedNextSlide, children: carouselItems.map((item, index) => jsx("div", { className: cn("flex-shrink-0", eyeTracking && gazeFocusedSlide === index && "consciousness-gaze-focused", predictive && predictedNextSlide === index && "consciousness-predicted-next", trackAchievements && slideInteractionCounts[index] > 0 && "consciousness-interacted"), style: { width: `${100 / slidesToShow}%`, paddingLeft: index === 0 ? 0 : undefined, paddingRight: index === (carouselItems?.length || 0) - 1 ? 0 : undefined }, "data-slide-index": index, "data-consciousness-slide": "true", "data-interaction-count": slideInteractionCounts[index] || 0, "data-gaze-focused": gazeFocusedSlide === index, "data-predicted": predictedNextSlide === index, children: jsx("div", { className: 'glass-h-full glass-w-full relative', children: item?.content }) }, item?.id)) }), showArrows && needsNavigation && jsxs(Fragment, { children: [jsx("div", { className: 'absolute left-4 glass-top-1/2 transform -translate-y-1/2 z-10', children: customPrevArrow ? jsx("div", { onClick: goToPrev, children: customPrevArrow }) : jsx(GlassButton, { variant: "secondary", size: "lg", onClick: goToPrev, disabled: !infinite && currentIndex === 0, className: 'glass-p-3 glass-shadow-lg hover:-translate-y-0.5 glass-ripple', "data-consciousness-nav": "prev", children: jsx(ChevronLeft, { className: 'w-6 h-6' }) }) }), jsx("div", { className: 'absolute right-4 glass-top-1/2 transform -translate-y-1/2 z-10', children: customNextArrow ? jsx("div", { onClick: goToNext, children: customNextArrow }) : jsx(GlassButton, { variant: "secondary", size: "lg", onClick: goToNext, disabled: !infinite && currentIndex >= maxIndex, className: 'glass-p-3 glass-shadow-lg hover:-translate-y-0.5 glass-ripple', "data-consciousness-nav": "next", children: jsx(ChevronRight, { className: 'w-6 h-6' }) }) })] }), autoPlay && needsNavigation && jsx("div", { className: 'absolute top-4 right-4 z-10', children: jsx(GlassButton, { variant: "secondary", size: "sm", onClick: e => setIsPlaying(!isPlaying), className: "glass-p-2", "data-consciousness-control": "play-pause", "data-adaptive-interval": adaptiveAutoPlayInterval, children: isPlaying ? jsx(Pause, { className: 'w-4 h-4' }) : jsx(Play, { className: 'w-4 h-4' }) }) }), showFullscreen && jsx("div", { className: 'absolute top-4 left-4 z-10', children: jsx(GlassButton, { variant: "secondary", size: "sm", onClick: e => setIsFullscreen(!isFullscreen), className: "glass-p-2", "data-consciousness-control": "fullscreen", children: isFullscreen ? jsx(Minimize2, { className: 'w-4 h-4' }) : jsx(Maximize2, { className: 'w-4 h-4' }) }) })] }), showIndicators && needsNavigation && jsx("div", { className: "glass-px-6 glass-py-4", "data-consciousness-indicators": "true", children: jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between glass-text-sm text-primary/80', children: [jsxs("span", { children: [currentIndex + 1, " /", " ", Math.ceil(totalItems / slidesToScroll), predictive && predictedNextSlide !== null && jsxs("span", { className: 'glass-ml-2 glass-text-xs text-primary/80', children: ["Next: ", predictedNextSlide + 1] })] }), jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsxs("span", { children: ["Slide ", currentIndex + 1, adaptive && adaptiveAutoPlayInterval !== autoPlayInterval && jsxs("span", { className: 'glass-ml-1 glass-text-xs text-primary/80', children: ["(", Math.round(adaptiveAutoPlayInterval / 1000), "s)"] })] }), jsx("div", { className: 'w-32 h-1 glass-surface-subtle/20 glass-radius-full overflow-hidden', children: jsx("div", { className: cn("h-full bg-primary transition-all duration-300", adaptive && "consciousness-adaptive-progress"), style: { width: `${(currentIndex + 1) / Math.ceil(totalItems / slidesToScroll) * 100}%`, transitionDuration: adaptive ? `${adaptiveAutoPlayInterval / 10}ms` : "300ms" } }) })] })] }) }), showDots && needsNavigation && jsx("div", { className: "glass-px-6 glass-py-4", "data-consciousness-dots": "true", children: jsx("div", { className: "glass-flex glass-justify-center glass-gap-2", children: Array.from({ length: Math.ceil(totalItems / slidesToScroll) }).map((_, index) => { const slideIndex = index * slidesToScroll; const isActive = Math.floor(currentIndex / slidesToScroll) === index; const isPredicted = predictive && predictedNextSlide === slideIndex; const isInteracted = trackAchievements && slideInteractionCounts[slideIndex] > 0; return jsx("button", { onClick: e => goToSlide(slideIndex), className: cn("w-3 h-3 glass-radius-full transition-all duration-200", isActive && "bg-primary scale-125", !isActive && "bg-black/40 hover:bg-black/60 border border-white/30 hover:border-white/50", isPredicted && "consciousness-predicted-dot", isInteracted && "consciousness-interacted-dot", eyeTracking && gazeFocusedSlide === slideIndex && "consciousness-gaze-focused-dot"), "data-slide-index": slideIndex, "data-consciousness-dot": "true", "data-predicted": isPredicted, "data-interacted": isInteracted, "data-gaze-focused": gazeFocusedSlide === slideIndex }, index); }) }) }), carouselItems[currentIndex]?.title && jsx("div", { className: "glass-px-6 glass-py-4 glass-border-t glass-border-white/10", children: jsxs("div", { className: 'text-center', children: [jsx("h3", { className: 'glass-text-lg font-semibold text-primary mb-1', children: carouselItems[currentIndex].title }), carouselItems[currentIndex]?.description && jsx("p", { className: 'glass-text-sm text-primary/70', children: carouselItems[currentIndex].description })] }) })] }) }) }); }); GlassCarousel.displayName = "GlassCarousel"; export { GlassCarousel, GlassCarousel as default }; //# sourceMappingURL=GlassCarousel.js.map