aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
439 lines (436 loc) • 19.9 kB
JavaScript
'use client';
import { jsx, jsxs } from 'react/jsx-runtime';
import { cn } from '../../lib/utilsComprehensive.js';
import { Filter, Activity, MoreHorizontal, Clock, AlertCircle, CheckCircle, XCircle, Info, Settings, User } from 'lucide-react';
import { useState, useMemo } 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 { CardHeader, CardTitle, CardContent } from '../card/index.js';
import '../data-display/GlassAccordion.js';
import '../data-display/GlassAlert.js';
import '../data-display/GlassAvatar.js';
import { GlassBadge } from '../data-display/GlassBadge.js';
import '../data-display/GlassBadgeLine.js';
import '../data-display/GlassDataGrid.js';
import '../data-display/GlassDataTable.js';
import '../data-display/GlassHeatmap.js';
import '../data-display/GlassLoadingSkeleton.js';
import '../data-display/GlassProgress.js';
import '../data-display/GlassTimeline.js';
import '../data-display/GlassSkeleton.js';
import '../data-display/GlassNotificationCenter.js';
import '../data-display/GlassAnimatedNumber.js';
import { GlassCard } from '../card/GlassCard.js';
/**
* GlassActivityFeed component
* A glassmorphism activity timeline with filtering and grouping
*/
const GlassActivityFeed = ({
// TODO: Integrate ContrastGuard for table cells, list items, badges, card titles, and other text content for WCAG AA compliance
activities,
title = "Activity Feed",
subtitle,
maxItems,
showFilters = true,
showCategories = true,
showTimestamps = true,
showAvatars = true,
compact = false,
loading = false,
emptyMessage = "No recent activity",
filterBy = [],
groupByDate = true,
showLoadMore = false,
onLoadMore,
onActivityClick,
className,
...props
}) => {
const [selectedFilter, setSelectedFilter] = useState("all");
const [visibleCount, setVisibleCount] = useState(maxItems || 10);
// Get activity type icon and color
const getActivityTypeConfig = type => {
switch (type) {
case "user":
return {
icon: User,
color: "text-blue-400",
bgColor: "bg-blue-500/20",
borderColor: "border-blue-500/30"
};
case "system":
return {
icon: Settings,
color: "text-purple-400",
bgColor: "bg-purple-500/20",
borderColor: "border-purple-500/30"
};
case "notification":
return {
icon: Info,
color: "text-cyan-400",
bgColor: "bg-cyan-500/20",
borderColor: "border-cyan-500/30"
};
case "error":
return {
icon: XCircle,
color: "text-red-400",
bgColor: "bg-red-500/20",
borderColor: "border-red-500/30"
};
case "success":
return {
icon: CheckCircle,
color: "text-green-400",
bgColor: "bg-green-500/20",
borderColor: "border-green-500/30"
};
case "warning":
return {
icon: AlertCircle,
color: "text-yellow-400",
bgColor: "bg-yellow-500/20",
borderColor: "border-yellow-500/30"
};
default:
return {
icon: Activity,
color: "glass-text-secondary",
bgColor: "bg-gray-500/20",
borderColor: "border-gray-500/30"
};
}
};
// Format timestamp
const formatTimestamp = date => {
const now = new Date();
const diff = now.getTime() - date.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 ago`;
if (hours < 24) return `${hours}h ago`;
if (days === 1) return "Yesterday";
if (days < 7) return `${days}d ago`;
return date.toLocaleDateString();
};
// Group activities by date
const groupedActivities = useMemo(() => {
if (!groupByDate) return {
"All Activities": activities
};
const groups = {};
activities.forEach(activity => {
const date = activity.timestamp.toDateString();
if (!groups[date]) {
groups[date] = [];
}
groups[date].push(activity);
});
return groups;
}, [activities, groupByDate]);
// Filter activities
const filteredActivities = useMemo(() => {
let filtered = activities;
if (selectedFilter !== "all") {
filtered = filtered.filter(activity => activity.type === selectedFilter);
}
if (filterBy && filterBy.length > 0) {
filtered = filtered.filter(activity => filterBy.includes(activity.type));
}
return filtered.slice(0, visibleCount);
}, [activities, selectedFilter, filterBy, visibleCount]);
// Get available filter options
const filterOptions = useMemo(() => {
const types = new Set(activities.map(activity => activity.type));
return ["all", ...Array.from(types)];
}, [activities]);
// Handle load more
const handleLoadMore = () => {
setVisibleCount(prev => prev + (maxItems || 10));
onLoadMore?.();
};
// Loading skeleton
if (loading) {
return jsx(GlassCard, {
"data-glass-component": true,
className: cn("glass-p-6", className),
children: jsxs("div", {
className: 'animate-pulse glass-auto-gap glass-auto-gap-lg',
children: [jsx("div", {
className: 'h-6 glass-surface-subtle/20 glass-radius-md w-48'
}), jsx("div", {
className: "glass-auto-gap glass-auto-gap-md",
children: Array.from({
length: 5
}).map((_, i) => jsxs("div", {
className: "glass-flex glass-gap-3",
children: [jsx("div", {
className: 'w-8 h-8 glass-surface-subtle/20 glass-radius-full glass-flex-shrink-0'
}), jsxs("div", {
className: "glass-flex-1 glass-auto-gap glass-auto-gap-sm",
children: [jsx("div", {
className: 'h-4 glass-surface-subtle/20 glass-radius-md w-3-4'
}), jsx("div", {
className: 'h-3 glass-surface-subtle/20 glass-radius-md w-1-2'
})]
})]
}, i))
})]
})
});
}
return jsx(MotionFramer, {
preset: "fadeIn",
className: "glass-w-full",
children: jsxs(GlassCard, {
className: cn("overflow-hidden", className),
...props,
children: [jsx(CardHeader, {
className: 'pb-4',
children: jsxs("div", {
className: "glass-flex glass-items-center glass-justify-between",
children: [jsxs("div", {
children: [jsx(CardTitle, {
className: 'text-primary glass-text-xl font-semibold',
children: title
}), subtitle && jsx("p", {
className: 'glass-text-sm text-primary/60 glass-mt-1',
children: subtitle
})]
}), showFilters && filterOptions.length > 1 && jsxs("div", {
className: "glass-flex glass-items-center glass-gap-2",
children: [jsx(Filter, {
className: 'w-4 h-4 text-primary/60'
}), jsxs("select", {
value: selectedFilter,
onChange: e => setSelectedFilter(e.target.value),
className: 'bg-glass-fill ring-1 ring-white/10 glass-radius-md glass-px-3 glass-py-1 glass-text-sm text-primary focus:outline-none focus:ring-white/30',
children: [jsx("option", {
value: "all",
children: "All Types"
}), filterOptions.slice(1).map(type => jsx("option", {
value: type,
children: type.charAt(0).toUpperCase() + type.slice(1)
}, type))]
})]
})]
})
}), jsx(CardContent, {
className: 'pt-0',
children: filteredActivities.length === 0 ? jsxs("div", {
className: "glass-flex glass-flex-col glass-items-center glass-justify-center glass-py-12",
children: [jsx(Activity, {
className: 'w-12 h-12 text-primary/40 mb-4'
}), jsx("p", {
className: 'text-primary/60 text-center',
children: emptyMessage
})]
}) : jsxs("div", {
className: "glass-auto-gap glass-auto-gap-lg",
children: [groupByDate ? Object.entries(groupedActivities).map(([date, items]) => jsxs("div", {
children: [groupByDate && items.length > 0 && jsxs("div", {
className: 'glass-flex glass-items-center glass-gap-2 mb-3',
children: [jsx("div", {
className: 'h-px glass-surface-subtle/20 glass-flex-1'
}), jsx("span", {
className: 'glass-text-xs text-primary/60 font-medium glass-px-2 glass-py-1 glass-surface-subtle/10 glass-radius-md',
children: new Date(date).toLocaleDateString(undefined, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric"
})
}), jsx("div", {
className: 'h-px glass-surface-subtle/20 glass-flex-1'
})]
}), jsx("div", {
className: "glass-auto-gap glass-auto-gap-md",
children: items.filter(activity => {
if (selectedFilter === "all") return true;
return activity.type === selectedFilter;
}).slice(0, visibleCount).map((activity, index) => {
const config = getActivityTypeConfig(activity.type);
const IconComponent = config.icon;
return jsxs("div", {
className: cn("flex glass-gap-3 glass-p-3 glass-radius-lg border transition-all duration-200 animate-slide-in-up", "hover:bg-white/5 cursor-pointer glass-foundation-complete glass-backdrop-blur-md bg-transparent border-white/40 shadow-2xl", config.bgColor, config.borderColor, compact && "glass-p-2"),
style: {
animationDelay: `${Math.min(index, 15) * 50}ms`,
animationFillMode: "both"
},
onClick: e => onActivityClick?.(activity),
children: [jsx("div", {
className: cn("flex-shrink-0 w-8 h-8 glass-radius-full flex items-center justify-center", config.bgColor, compact && "w-6 h-6"),
children: activity.icon ? jsx("span", {
className: 'w-4 h-4',
children: activity.icon
}) : jsx(IconComponent, {
className: cn("w-4 h-4", config.color, compact && "w-3 h-3")
})
}), jsxs("div", {
className: "glass-flex-1 glass-min-w-0",
children: [jsxs("div", {
className: "glass-flex glass-items-start glass-justify-between glass-gap-2 glass-min-w-0",
children: [jsxs("div", {
className: "glass-flex-1 glass-min-w-0",
children: [jsx("p", {
className: cn("glass-text-primary font-medium truncate", compact ? "glass-text-sm" : "glass-text-base"),
title: activity.title,
children: activity.title
}), activity.description && jsx("p", {
className: cn("glass-text-primary/70 glass-mt-1 line-clamp-2", compact ? "glass-text-xs" : "glass-text-sm"),
title: activity.description,
children: activity.description
})]
}), activity.actions && activity.actions.length > 0 && jsx("div", {
className: "glass-flex glass-items-center glass-gap-1",
children: activity.actions.map((action, actionIndex) => jsx(GlassButton, {
variant: "ghost",
size: "sm",
onClick: e => {
e.stopPropagation();
action.onClick();
},
className: "glass-p-1",
children: action.icon || jsx(MoreHorizontal, {
className: 'w-3 h-3'
})
}, actionIndex))
})]
}), jsxs("div", {
className: "glass-flex glass-items-center glass-gap-3 glass-mt-2",
children: [activity.user && showAvatars && jsxs("div", {
className: "glass-flex glass-items-center glass-gap-2",
children: [activity.user.avatar ? jsx("img", {
src: activity.user.avatar,
alt: activity.user.name,
className: 'w-5 h-5 glass-radius-full'
}) : jsx("div", {
className: 'w-5 h-5 glass-radius-full glass-surface-subtle/20 glass-flex glass-items-center glass-justify-center',
children: jsx("span", {
className: 'glass-text-xs text-primary/80 font-medium',
children: activity.user.name.charAt(0).toUpperCase()
})
}), jsx("span", {
className: 'glass-text-xs text-primary/60',
children: activity.user.name
})]
}), showTimestamps && jsxs("span", {
className: 'glass-text-xs text-primary/50 glass-flex glass-items-center glass-gap-1',
children: [jsx(Clock, {
className: 'w-3 h-3'
}), formatTimestamp(activity.timestamp)]
}), activity.category && showCategories && jsx(GlassBadge, {
variant: "secondary",
size: "sm",
className: 'truncate max-w-[100px]',
title: activity.category,
children: activity.category.length > 12 ? `${activity.category.slice(0, 12)}...` : activity.category
}), activity.tags && activity.tags.length > 0 && jsxs("div", {
className: "glass-flex glass-flex-wrap glass-gap-1 glass-items-center",
children: [activity.tags.slice(0, compact ? 1 : 2).map((tag, tagIndex) => jsx(GlassBadge, {
variant: "outline",
size: "sm",
className: 'truncate max-w-[80px]',
title: tag,
children: tag.length > 8 ? `${tag.slice(0, 8)}...` : tag
}, tagIndex)), activity.tags.length > (compact ? 1 : 2) && jsxs(GlassBadge, {
variant: "secondary",
size: "sm",
className: 'opacity-60',
children: ["+", activity.tags.length - (compact ? 1 : 2)]
})]
})]
})]
})]
}, activity.id);
})
})]
}, date)) :
// Ungrouped view
jsx("div", {
className: "glass-auto-gap glass-auto-gap-md",
children: filteredActivities.map((activity, index) => {
const config = getActivityTypeConfig(activity.type);
const IconComponent = config.icon;
return jsxs(MotionFramer, {
preset: "slideUp",
delay: index * 50,
className: cn("flex glass-gap-3 glass-p-3 glass-radius-lg border transition-all duration-200", "hover:bg-white/5 cursor-pointer", config.bgColor, config.borderColor, compact && "glass-p-2"),
onClick: e => onActivityClick?.(activity),
children: [jsx("div", {
className: cn("flex-shrink-0 w-8 h-8 glass-radius-full flex items-center justify-center", config.bgColor, compact && "w-6 h-6"),
children: activity.icon ? jsx("span", {
className: 'w-4 h-4',
children: activity.icon
}) : jsx(IconComponent, {
className: cn("w-4 h-4", config.color, compact && "w-3 h-3")
})
}), jsxs("div", {
className: "glass-flex-1 glass-min-w-0",
children: [jsx("div", {
className: "glass-flex glass-items-start glass-justify-between glass-gap-2 glass-min-w-0",
children: jsxs("div", {
className: "glass-flex-1 glass-min-w-0",
children: [jsx("p", {
className: cn("glass-text-primary font-medium truncate", compact ? "glass-text-sm" : "glass-text-base"),
title: activity.title,
children: activity.title
}), activity.description && jsx("p", {
className: cn("glass-text-primary/70 glass-mt-1 line-clamp-2", compact ? "glass-text-xs" : "glass-text-sm"),
title: activity.description,
children: activity.description
})]
})
}), jsxs("div", {
className: "glass-flex glass-items-center glass-gap-3 glass-mt-2",
children: [activity.user && showAvatars && jsxs("div", {
className: "glass-flex glass-items-center glass-gap-2",
children: [activity.user.avatar ? jsx("img", {
src: activity.user.avatar,
alt: activity.user.name,
className: 'w-5 h-5 glass-radius-full'
}) : jsx("div", {
className: 'w-5 h-5 glass-radius-full glass-surface-subtle/20 glass-flex glass-items-center glass-justify-center',
children: jsx("span", {
className: 'glass-text-xs text-primary/80 font-medium',
children: activity.user.name.charAt(0).toUpperCase()
})
}), jsx("span", {
className: 'glass-text-xs text-primary/60',
children: activity.user.name
})]
}), showTimestamps && jsxs("span", {
className: 'glass-text-xs text-primary/50 glass-flex glass-items-center glass-gap-1',
children: [jsx(Clock, {
className: 'w-3 h-3'
}), formatTimestamp(activity.timestamp)]
})]
})]
})]
}, activity.id);
})
}), showLoadMore && visibleCount < activities.length && jsx("div", {
className: 'glass-flex glass-justify-center mt-6',
children: jsx(GlassButton, {
variant: "outline",
onClick: handleLoadMore,
children: "Load More Activities"
})
})]
})
})]
})
});
};
export { GlassActivityFeed, GlassActivityFeed as default };
//# sourceMappingURL=GlassActivityFeed.js.map