UNPKG

aura-glass

Version:

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

467 lines (464 loc) 16.7 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { useReducedMotion } from '../../hooks/useReducedMotion.js'; import { forwardRef, useState, useEffect, useMemo } from 'react'; import { AnimatePresence, motion } from 'framer-motion'; import '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import { OptimizedGlassCore } from '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import '../../primitives/motion/MotionFramer.js'; import { useA11yId } from '../../utils/a11y.js'; import { useMotionPreference } from '../../hooks/useMotionPreference.js'; import { createGlassStyle } from '../../utils/createGlassStyle.js'; const engagementLevels = { low: { color: "var(--glass-gray-500)", icon: "📊" }, medium: { color: "var(--glass-color-success)", icon: "📈" }, high: { color: "var(--glass-color-warning)", icon: "🔥" }, viral: { color: "var(--glass-color-danger)", icon: "🚀" } }; const GlassSocialFeed = /*#__PURE__*/forwardRef(({ posts, currentUserId, showInteractions = true, showTimestamps = true, showMedia = true, showTags = true, compactMode = false, maxHeight, infiniteScroll = false, realTimeUpdates = false, sortBy = "timestamp", filterBy = "all", onLike, onShare, onComment, onUserClick, onPostClick, onLoadMore, className = "", ...props }, ref) => { const prefersReducedMotion = useReducedMotion(); const [likedPosts, setLikedPosts] = useState(new Set()); const [sharedPosts, setSharedPosts] = useState(new Set()); const [expandedPosts, setExpandedPosts] = useState(new Set()); const [simulatedPosts, setSimulatedPosts] = useState(posts); useA11yId("glass-social-feed"); // Motion preference hook const { shouldAnimate } = useMotionPreference(); // Helper function to respect motion preferences const respectMotionPreference = config => shouldAnimate ? config : { duration: 0 }; // Simulated real-time updates useEffect(() => { if (!realTimeUpdates) return; const interval = setInterval(() => { setSimulatedPosts(prev => prev.map(post => ({ ...post, likes: post.likes + (Math.random() < 0.3 ? Math.floor(Math.random() * 3) : 0), comments: post.comments + (Math.random() < 0.2 ? 1 : 0), shares: post.shares + (Math.random() < 0.15 ? 1 : 0) }))); }, 5000); return () => clearInterval(interval); }, [realTimeUpdates]); const processedPosts = useMemo(() => { let filtered = [...simulatedPosts]; // Apply filters switch (filterBy) { case "following": // In a real app, this would filter by followed users filtered = filtered.filter(post => post.author.verified); break; case "liked": filtered = filtered.filter(post => likedPosts.has(post.id)); break; } // Apply sorting switch (sortBy) { case "likes": filtered.sort((a, b) => b.likes - a.likes); break; case "engagement": filtered.sort((a, b) => b.likes + b.comments + b.shares - (a.likes + a.comments + a.shares)); break; default: filtered.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); } return filtered; }, [simulatedPosts, filterBy, sortBy, likedPosts]); const formatTimeAgo = timestamp => { const now = new Date(); const diff = now.getTime() - timestamp.getTime(); const minutes = Math.floor(diff / 60000); const hours = Math.floor(diff / 3600000); const days = Math.floor(diff / 86400000); if (minutes < 1) return "Just now"; if (minutes < 60) return `${minutes}m`; if (hours < 24) return `${hours}h`; if (days < 7) return `${days}d`; return timestamp.toLocaleDateString(); }; const getEngagementLevel = post => { const total = post.likes + post.comments + post.shares; if (total > 1000) return "viral"; if (total > 100) return "high"; if (total > 10) return "medium"; return "low"; }; const handleLike = postId => { setLikedPosts(prev => { const newSet = new Set(prev); if (newSet.has(postId)) { newSet.delete(postId); } else { newSet.add(postId); } return newSet; }); onLike?.(postId); }; const handleShare = postId => { setSharedPosts(prev => new Set(prev).add(postId)); onShare?.(postId); }; const handlePostExpand = postId => { setExpandedPosts(prev => { const newSet = new Set(prev); if (newSet.has(postId)) { newSet.delete(postId); } else { newSet.add(postId); } return newSet; }); }; const PostCard = ({ post, index }) => { const isExpanded = expandedPosts.has(post.id); const isLiked = likedPosts.has(post.id); const isShared = sharedPosts.has(post.id); const engagement = getEngagementLevel(post); const shouldTruncate = !compactMode && post.content.length > 200; return jsxs(motion.div, { layout: true, initial: { opacity: 0, y: 20 }, animate: prefersReducedMotion ? {} : { opacity: 1, y: 0 }, exit: { opacity: 0, y: -20 }, transition: respectMotionPreference({ duration: 0.3, delay: index * 0.05 }), className: ` relative p-4 rounded-lg cursor-pointer transition-all duration-200 ${createGlassStyle({ variant: "default" })} hover:bg-white/5 border border-white/10 `, onClick: () => onPostClick?.(post.id), children: [jsxs("div", { className: 'glass-flex glass-items-start space-x-3 mb-3', children: [jsxs(motion.div, { className: 'relative cursor-pointer', onClick: e => { e.stopPropagation(); onUserClick?.(post.author.id); }, whileHover: { scale: 1.05 }, whileTap: { scale: 0.95 }, children: [jsx("div", { className: ` ${compactMode ? "w-8 h-8" : "w-12 h-12"} rounded-full bg-gradient-to-br from-gray-300 to-gray-500 flex items-center justify-center text-white font-semibold ${createGlassStyle({ variant: "default" })} `, children: post.author.avatar ? jsx("img", { src: post.author.avatar, alt: post.author.name, className: 'glass-w-full glass-h-full glass-radius-full object-cover' }) : post.author.name.charAt(0).toUpperCase() }), post.author.verified && jsx("div", { className: 'absolute -bottom-1 -right-1 w-4 h-4 glass-surface-blue glass-radius-full glass-flex glass-items-center glass-justify-center', children: jsx("span", { className: 'text-primary glass-text-xs', children: "\u2713" }) })] }), jsxs("div", { className: "glass-flex-1 glass-min-w-0", children: [jsxs("div", { className: 'glass-flex glass-items-center space-x-2', children: [jsx("h4", { className: ` font-semibold text-white/90 truncate ${compactMode ? "text-sm" : "text-base"} `, children: post.author.name }), jsxs("span", { className: ` text-white/60 ${compactMode ? "text-xs" : "text-sm"} `, children: ["@", post.author.username] }), jsx("div", { className: 'w-2 h-2 glass-radius-full', style: { backgroundColor: engagementLevels[engagement].color }, title: `${engagement} engagement` })] }), showTimestamps && jsx("p", { className: ` text-white/50 ${compactMode ? "text-xs" : "text-sm"} `, children: formatTimeAgo(post.timestamp) })] })] }), jsxs("div", { className: 'mb-3', children: [jsxs("p", { className: ` text-white/90 leading-relaxed ${compactMode ? "text-sm" : "text-base"} `, children: [shouldTruncate && !isExpanded ? `${post.content.slice(0, 200)}...` : post.content, shouldTruncate && jsx("button", { onClick: e => { e.stopPropagation(); handlePostExpand(post.id); }, className: 'ml-2 text-primary hover:glass-text-secondary glass-text-sm font-medium glass-focus glass-touch-target glass-contrast-guard', children: isExpanded ? "Show less" : "Show more" })] }), showTags && post.tags && post.tags.length > 0 && jsx("div", { className: 'glass-flex glass-flex-wrap glass-gap-2 mt-2', children: post.tags.map(tag => jsxs("span", { className: ` px-2 py-1 text-xs rounded-full bg-blue-500/20 text-blue-300 hover:bg-blue-500/30 cursor-pointer transition-colors duration-200 `, children: ["#", tag] }, tag)) })] }), showMedia && post.media && post.media.length > 0 && jsx("div", { className: 'mb-3 glass-radius-lg overflow-hidden', children: jsx("div", { className: ` grid gap-2 ${post.media.length === 1 ? "grid-cols-1" : "grid-cols-2"} `, children: post.media.slice(0, 4).map((media, mediaIndex) => jsxs("div", { className: 'relative aspect-square glass-surface-subtle/5 glass-radius-lg overflow-hidden', children: [media.type === "image" ? jsx("img", { src: media.url, alt: media.alt || "Post media", className: 'glass-w-full glass-h-full object-cover hover:scale-105 transition-transform duration-300' }) : media.type === "video" ? jsx("video", { src: media.url, poster: media.thumbnail, className: 'glass-w-full glass-h-full object-cover', controls: true }) : jsx("img", { src: media.url, alt: media.alt || "GIF", className: 'glass-w-full glass-h-full object-cover' }), post.media && post.media.length > 4 && mediaIndex === 3 && jsx("div", { className: 'absolute inset-0 glass-surface-dark/60 glass-flex glass-items-center glass-justify-center', children: jsxs("span", { className: 'text-primary font-semibold', children: ["+", post.media.length - 3, " more"] }) })] }, mediaIndex)) }) }), showInteractions && jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between pt-3 glass-border-t glass-border-white/10', children: [jsxs("div", { className: 'glass-flex glass-items-center space-x-6', children: [jsxs(motion.button, { onClick: e => { e.stopPropagation(); handleLike(post.id); }, className: ` flex items-center space-x-2 text-sm transition-colors duration-200 ${isLiked ? "text-red-400" : "text-white/60 hover:text-red-400"} `, whileHover: { scale: 1.05 }, whileTap: { scale: 0.95 }, children: [jsx("span", { children: isLiked ? "❤️" : "🤍" }), jsx("span", { children: post.likes + (isLiked ? 1 : 0) })] }), jsxs("button", { onClick: e => { e.stopPropagation(); onComment?.(post.id); }, className: 'glass-flex glass-items-center space-x-2 glass-text-sm text-primary/60 hover:text-primary transition-colors duration-200 glass-focus glass-touch-target glass-contrast-guard', children: [jsx("span", { children: "\uD83D\uDCAC" }), jsx("span", { children: post.comments })] }), jsxs(motion.button, { onClick: e => { e.stopPropagation(); handleShare(post.id); }, className: ` flex items-center space-x-2 text-sm transition-colors duration-200 ${isShared ? "text-green-400" : "text-white/60 hover:text-green-400"} `, whileHover: { scale: 1.05 }, whileTap: { scale: 0.95 }, children: [jsx("span", { children: "\uD83D\uDD04" }), jsx("span", { children: post.shares + (isShared ? 1 : 0) })] })] }), jsxs("div", { className: 'glass-flex glass-items-center space-x-2 glass-text-sm text-primary/50', children: [jsx("span", { children: engagementLevels[engagement].icon }), jsx("span", { children: post.likes + post.comments + post.shares })] })] })] }); }; return jsx(OptimizedGlassCore, { ref: ref, variant: "frosted", className: `${className}`, style: { maxHeight }, ...props, children: jsxs("div", { className: 'glass-p-4 space-y-4', children: [jsxs("div", { className: "glass-flex glass-items-center glass-justify-between", children: [jsxs("h2", { className: 'glass-text-lg font-semibold text-primary/90', children: ["Social Feed (", processedPosts.length, ")"] }), jsxs("div", { className: 'glass-flex glass-items-center space-x-2 glass-text-sm', children: [realTimeUpdates && jsxs("div", { className: 'glass-flex glass-items-center space-x-1 text-primary', children: [jsx("div", { className: 'w-2 h-2 glass-surface-green glass-radius-full animate-pulse' }), jsx("span", { children: "Live" })] }), jsxs("select", { value: sortBy, onChange: e => { /* Would update sortBy in real implementation */ }, className: 'glass-surface-subtle/10 glass-border glass-border-white/20 glass-radius glass-px-2 glass-py-1 text-primary glass-text-sm glass-focus glass-touch-target glass-contrast-guard', children: [jsx("option", { value: "timestamp", children: "Latest" }), jsx("option", { value: "likes", children: "Most Liked" }), jsx("option", { value: "engagement", children: "Most Engaging" })] })] })] }), jsxs("div", { className: ` space-y-4 ${maxHeight ? "overflow-y-auto" : ""} `, children: [jsx(AnimatePresence, { children: processedPosts.map((post, index) => jsx(PostCard, { post: post, index: index }, post.id)) }), infiniteScroll && onLoadMore && jsx(motion.button, { onClick: onLoadMore, className: ` w-full p-4 rounded-lg text-sm font-medium text-white/70 hover:text-white hover:bg-white/5 transition-colors duration-200 ${createGlassStyle({ variant: "default" })} border border-white/10 `, whileHover: { scale: 1.02 }, whileTap: { scale: 0.98 }, children: "Load More Posts" })] }), processedPosts.length === 0 && jsxs("div", { className: 'text-center glass-py-12', children: [jsx("div", { className: 'text-6xl mb-4', children: "\uD83D\uDCF1" }), jsx("h3", { className: 'glass-text-lg font-semibold text-primary/70 mb-2', children: "No posts to show" }), jsx("p", { className: 'text-primary/50', children: filterBy === "liked" ? "You haven't liked any posts yet" : "Your feed is empty. Try following some users!" })] })] }) }); }); export { GlassSocialFeed }; //# sourceMappingURL=GlassSocialFeed.js.map