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