aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
502 lines (499 loc) • 21.4 kB
JavaScript
'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