Initial commit: FunConnect project with server, relay, client and admin panel
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
110
client/ui/src/components/Avatar.tsx
Normal file
110
client/ui/src/components/Avatar.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { cn, generateAvatarColor, getInitials } from '../lib/utils';
|
||||
|
||||
interface AvatarProps {
|
||||
seed: string;
|
||||
name: string;
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
className?: string;
|
||||
showOnline?: boolean;
|
||||
isOnline?: boolean;
|
||||
}
|
||||
|
||||
export function Avatar({
|
||||
seed,
|
||||
name,
|
||||
size = 'md',
|
||||
className,
|
||||
showOnline = false,
|
||||
isOnline = false,
|
||||
}: AvatarProps) {
|
||||
const sizeClasses = {
|
||||
sm: 'w-6 h-6 text-xs',
|
||||
md: 'w-8 h-8 text-sm',
|
||||
lg: 'w-10 h-10 text-base',
|
||||
xl: 'w-12 h-12 text-lg',
|
||||
};
|
||||
|
||||
const dotSizeClasses = {
|
||||
sm: 'w-1.5 h-1.5 -bottom-0 -right-0',
|
||||
md: 'w-2 h-2 -bottom-0.5 -right-0.5',
|
||||
lg: 'w-2.5 h-2.5 -bottom-0.5 -right-0.5',
|
||||
xl: 'w-3 h-3 -bottom-1 -right-1',
|
||||
};
|
||||
|
||||
const bgColor = generateAvatarColor(seed);
|
||||
const initials = getInitials(name);
|
||||
|
||||
return (
|
||||
<div className={cn('relative flex-shrink-0', className)}>
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-full flex items-center justify-center font-semibold text-white',
|
||||
sizeClasses[size]
|
||||
)}
|
||||
style={{ backgroundColor: bgColor }}
|
||||
>
|
||||
{initials}
|
||||
</div>
|
||||
{showOnline && (
|
||||
<span
|
||||
className={cn(
|
||||
'absolute rounded-full border-2 border-bg-secondary',
|
||||
dotSizeClasses[size],
|
||||
isOnline ? 'bg-accent-green shadow-[0_0_6px_#4ade80]' : 'bg-text-muted'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface AvatarGroupProps {
|
||||
users: Array<{ seed: string; name: string }>;
|
||||
max?: number;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
export function AvatarGroup({ users, max = 4, size = 'md' }: AvatarGroupProps) {
|
||||
const displayed = users.slice(0, max);
|
||||
const remaining = users.length - max;
|
||||
|
||||
const overlapClasses = {
|
||||
sm: '-ml-2',
|
||||
md: '-ml-3',
|
||||
lg: '-ml-4',
|
||||
};
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'w-6 h-6 text-xs',
|
||||
md: 'w-8 h-8 text-sm',
|
||||
lg: 'w-10 h-10 text-base',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{displayed.map((user, index) => (
|
||||
<div
|
||||
key={user.seed}
|
||||
className={cn(
|
||||
'rounded-full border-2 border-bg-secondary',
|
||||
index > 0 && overlapClasses[size]
|
||||
)}
|
||||
style={{ zIndex: displayed.length - index }}
|
||||
>
|
||||
<Avatar seed={user.seed} name={user.name} size={size} />
|
||||
</div>
|
||||
))}
|
||||
{remaining > 0 && (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-full bg-bg-tertiary flex items-center justify-center font-medium text-text-secondary border-2 border-bg-secondary',
|
||||
overlapClasses[size],
|
||||
sizeClasses[size]
|
||||
)}
|
||||
>
|
||||
+{remaining}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user