UNPKG

aura-glass

Version:

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

504 lines (501 loc) 17.4 kB
'use client'; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import { Circle, Clock, Users, Activity, MapPin, MessageCircle, Phone, Video, MoreHorizontal } from 'lucide-react'; import { useState, useCallback } 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'; /** * GlassUserPresence component * Display user presence status with avatars and online indicators */ const GlassUserPresence = ({ currentUser, users, showOnlineCount = true, showRoles = false, showLocations = false, showActivities = false, enableActions = false, enableStatusUpdate = false, groupByStatus = false, compact = false, maxUsers, onUserClick, onUserAction, onStatusChange, className, ...props }) => { const [showAllUsers, setShowAllUsers] = useState(false); // Get status color and icon const getStatusConfig = useCallback(status => { switch (status) { case "online": return { color: "bg-green-400", borderColor: "border-green-400", textColor: "text-green-400", icon: Circle, label: "Online" }; case "away": return { color: "bg-yellow-400", borderColor: "border-yellow-400", textColor: "text-yellow-400", icon: Clock, label: "Away" }; case "busy": return { color: "bg-red-400", borderColor: "border-red-400", textColor: "text-red-400", icon: Circle, label: "Busy" }; case "invisible": return { color: "bg-gray-400", borderColor: "border-gray-400", textColor: "glass-text-secondary", icon: Circle, label: "Invisible" }; default: return { color: "bg-gray-400", borderColor: "border-gray-400", textColor: "glass-text-secondary", icon: Circle, label: "Offline" }; } }, []); // Get role badge useCallback(role => { switch (role) { case "admin": return jsx(GlassBadge, { variant: "destructive", size: "sm", children: "Admin" }); case "moderator": return jsx(GlassBadge, { variant: "warning", size: "sm", children: "Mod" }); case "member": return jsx(GlassBadge, { variant: "secondary", size: "sm", children: "Member" }); case "guest": return jsx(GlassBadge, { variant: "outline", size: "sm", children: "Guest" }); default: return null; } }, []); // Format last seen time useCallback(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(); }, []); // Handle status change const handleStatusChange = useCallback(status => { onStatusChange?.(status); }, [onStatusChange]); // Filter and sort users const processedUsers = users.filter(user => user.status !== "invisible" || user.id === currentUser?.id).sort((a, b) => { // Sort by status priority const statusOrder = { online: 0, away: 1, busy: 2, offline: 3, invisible: 4 }; const aOrder = statusOrder[a.status] ?? 3; const bOrder = statusOrder[b.status] ?? 3; if (aOrder !== bOrder) return aOrder - bOrder; // Then by name return a.name.localeCompare(b.name); }); const displayUsers = showAllUsers || !maxUsers ? processedUsers : processedUsers.slice(0, maxUsers); const onlineCount = users.filter(user => user.status === "online").length; // Group users by status const groupedUsers = processedUsers.reduce((groups, user) => { if (!groups[user.status]) { groups[user.status] = []; } groups[user.status].push(user); return groups; }, {}); return jsx(MotionFramer, { "data-glass-component": true, preset: "fadeIn", className: "glass-w-full", children: jsxs(GlassCard, { className: cn("overflow-hidden", className), ...props, children: [jsx(CardHeader, { className: 'pb-3', children: jsxs("div", { className: "glass-flex glass-items-center glass-justify-between", children: [jsxs(CardTitle, { className: 'text-primary glass-text-lg font-semibold glass-flex glass-items-center glass-gap-2', children: [jsx(Users, { className: 'w-5 h-5' }), "Team Presence"] }), showOnlineCount && jsxs(GlassBadge, { variant: "secondary", children: [onlineCount, " online"] })] }) }), jsxs(CardContent, { className: 'pt-0', children: [currentUser && enableStatusUpdate && jsx("div", { className: 'mb-6 glass-p-3 glass-surface-subtle/5 glass-radius-lg', children: jsxs("div", { className: "glass-flex glass-items-center glass-justify-between", children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-3", children: [jsxs("div", { className: 'relative', children: [jsx("div", { className: 'w-10 h-10 glass-radius-full glass-surface-subtle/20 glass-flex glass-items-center glass-justify-center', children: currentUser.avatar ? jsx("img", { src: currentUser.avatar, alt: currentUser.name, className: 'glass-w-full glass-h-full glass-radius-full object-cover' }) : jsx("span", { className: 'text-primary font-medium', children: currentUser.name.charAt(0).toUpperCase() }) }), jsx("div", { className: cn("absolute -bottom-1 -right-1 w-4 h-4 glass-radius-full border-2 border-black", getStatusConfig(currentUser.status).color) })] }), jsxs("div", { children: [jsx("p", { className: 'text-primary font-medium', children: currentUser.name }), jsx("p", { className: 'text-primary/60 glass-text-sm', children: "Set your status" })] })] }), jsx("div", { className: "glass-flex glass-gap-1", children: ["online", "away", "busy", "invisible"].map(status => { const config = getStatusConfig(status); const IconComponent = config.icon; return jsx(GlassButton, { variant: currentUser.status === status ? "primary" : "ghost", size: "sm", onClick: e => handleStatusChange(status), className: "glass-p-2", title: config.label, children: jsx(IconComponent, { className: 'w-3 h-3' }) }, status); }) })] }) }), groupByStatus ? // Grouped by status jsx("div", { className: "glass-gap-4", children: Object.entries(groupedUsers).map(([status, statusUsers]) => { const config = getStatusConfig(status); return jsxs("div", { children: [jsxs("div", { className: 'glass-flex glass-items-center glass-gap-2 mb-2', children: [jsx("div", { className: cn("w-2 h-2 glass-radius-full", config.color) }), jsxs("span", { className: 'text-primary/80 glass-text-sm font-medium', children: [config.label, " (", statusUsers.length, ")"] })] }), jsx("div", { className: "glass-gap-2", children: statusUsers.map(user => jsx(UserPresenceItem, { user: user, compact: compact, showRoles: showRoles, showLocations: showLocations, showActivities: showActivities, enableActions: enableActions, onUserClick: onUserClick, onUserAction: onUserAction }, user.id)) })] }, status); }) }) : // Flat list jsxs("div", { className: "glass-gap-2", children: [displayUsers.map(user => jsx(UserPresenceItem, { user: user, compact: compact, showRoles: showRoles, showLocations: showLocations, showActivities: showActivities, enableActions: enableActions, onUserClick: onUserClick, onUserAction: onUserAction }, user.id)), maxUsers && users.length > maxUsers && !showAllUsers && jsxs(GlassButton, { variant: "ghost", size: "sm", onClick: e => setShowAllUsers(true), className: "glass-w-full glass-mt-2", children: ["Show ", users.length - maxUsers, " more"] })] })] })] }) }); }; const UserPresenceItem = ({ user, compact = false, showRoles = false, showLocations = false, showActivities = false, enableActions = false, onUserClick, onUserAction }) => { const config = getStatusConfig(user.status); // Helper function (defined here since it's used in this component) function getStatusConfig(status) { switch (status) { case "online": return { color: "bg-green-400", textColor: "text-green-400", label: "Online" }; case "away": return { color: "bg-yellow-400", textColor: "text-yellow-400", label: "Away" }; case "busy": return { color: "bg-red-400", textColor: "text-red-400", label: "Busy" }; case "invisible": return { color: "bg-gray-400", textColor: "glass-text-secondary", label: "Invisible" }; default: return { color: "bg-gray-400", textColor: "glass-text-secondary", label: "Offline" }; } } const formatLastSeen = 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(); }; const getRoleBadge = role => { switch (role) { case "admin": return jsx(GlassBadge, { variant: "destructive", size: "sm", children: "Admin" }); case "moderator": return jsx(GlassBadge, { variant: "warning", size: "sm", children: "Mod" }); case "member": return jsx(GlassBadge, { variant: "secondary", size: "sm", children: "Member" }); case "guest": return jsx(GlassBadge, { variant: "outline", size: "sm", children: "Guest" }); default: return null; } }; return jsxs(MotionFramer, { preset: "fadeIn", className: cn("flex items-center glass-gap-3 glass-p-2 glass-radius-lg hover:bg-white/5 cursor-pointer transition-all duration-200", compact && "glass-p-1"), onClick: e => onUserClick?.(user), children: [jsxs("div", { className: 'relative glass-flex-shrink-0', children: [jsx("div", { className: cn("glass-radius-full bg-white/20 flex items-center justify-center", compact ? "w-8 h-8" : "w-10 h-10"), children: user.avatar ? jsx("img", { src: user.avatar, alt: user.name, className: 'glass-w-full glass-h-full glass-radius-full object-cover' }) : jsx("span", { className: cn("glass-text-primary font-medium", compact ? "glass-text-sm" : "glass-text-base"), children: user.name.charAt(0).toUpperCase() }) }), jsx("div", { className: cn("absolute -bottom-1 -right-1 w-3 h-3 glass-radius-full border-2 border-black", config.color, compact && "w-2 h-2 -bottom-0.5 -right-0.5") })] }), jsxs("div", { className: "glass-flex-1 glass-min-w-0", children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx("p", { className: cn("glass-text-primary font-medium truncate", compact ? "glass-text-sm" : "glass-text-base"), children: user.name }), showRoles && getRoleBadge(user.role)] }), !compact && jsxs("div", { className: "glass-gap-1", children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx("span", { className: cn("glass-text-xs", config.textColor), children: config.label }), user.status === "offline" && user.lastSeen && jsxs(Fragment, { children: [jsx("span", { className: 'text-primary/40 glass-text-xs', children: "\u2022" }), jsx("span", { className: 'text-primary/60 glass-text-xs', children: formatLastSeen(user.lastSeen) })] })] }), user.customStatus && jsx("p", { className: 'text-primary/70 glass-text-xs truncate', children: user.customStatus }), showActivities && user.activity && jsxs("div", { className: 'glass-flex glass-items-center glass-gap-1 text-primary/60 glass-text-xs', children: [jsx(Activity, { className: 'w-3 h-3' }), jsx("span", { className: 'truncate', children: user.activity })] }), showLocations && user.location && jsxs("div", { className: 'glass-flex glass-items-center glass-gap-1 text-primary/60 glass-text-xs', children: [jsx(MapPin, { className: 'w-3 h-3' }), jsx("span", { className: 'truncate', children: user.location })] })] })] }), enableActions && jsxs("div", { className: "glass-flex glass-items-center glass-gap-1", children: [jsx(GlassButton, { variant: "ghost", size: "sm", onClick: e => { e.stopPropagation(); onUserAction?.(user.id, "message"); }, className: "glass-p-1", children: jsx(MessageCircle, { className: 'w-3 h-3' }) }), jsx(GlassButton, { variant: "ghost", size: "sm", onClick: e => { e.stopPropagation(); onUserAction?.(user.id, "call"); }, className: "glass-p-1", children: jsx(Phone, { className: 'w-3 h-3' }) }), jsx(GlassButton, { variant: "ghost", size: "sm", onClick: e => { e.stopPropagation(); onUserAction?.(user.id, "video"); }, className: "glass-p-1", children: jsx(Video, { className: 'w-3 h-3' }) }), jsx(GlassButton, { variant: "ghost", size: "sm", onClick: e => e.stopPropagation(), className: "glass-p-1", children: jsx(MoreHorizontal, { className: 'w-3 h-3' }) })] })] }); }; export { GlassUserPresence, GlassUserPresence as default }; //# sourceMappingURL=GlassUserPresence.js.map