add:ProfilePicture component and replace Avatar
Move avatar initials, fallback styling, and loading/loading-state UI into ProfilePicture and update components to use it. Export ProfilePicture from the lib index.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { Badge } from '$lib/components/badge';
|
||||
import LikeButton from '$lib/components/LikeButton.svelte';
|
||||
import VideoPlayer from '$lib/components/VideoPlayer.svelte';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '$lib/components/avatar';
|
||||
import ProfilePicture from '$lib/components/ProfilePicture.svelte';
|
||||
import { MoreHorizontal, MessageCircle, Share } from '@lucide/svelte';
|
||||
|
||||
interface FindCardProps {
|
||||
@@ -42,24 +42,17 @@
|
||||
function handleExplore() {
|
||||
onExplore?.(id);
|
||||
}
|
||||
|
||||
function getUserInitials(username: string): string {
|
||||
return username.slice(0, 2).toUpperCase();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="find-card">
|
||||
<!-- Post Header -->
|
||||
<div class="post-header">
|
||||
<div class="user-info">
|
||||
<Avatar class="avatar">
|
||||
{#if user.profilePictureUrl}
|
||||
<AvatarImage src={user.profilePictureUrl} alt={user.username} />
|
||||
{/if}
|
||||
<AvatarFallback class="avatar-fallback">
|
||||
{getUserInitials(user.username)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<ProfilePicture
|
||||
username={user.username}
|
||||
profilePictureUrl={user.profilePictureUrl}
|
||||
class="avatar"
|
||||
/>
|
||||
<div class="user-details">
|
||||
<div class="username">@{user.username}</div>
|
||||
{#if locationName}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '$lib/components/sheet';
|
||||
import LikeButton from '$lib/components/LikeButton.svelte';
|
||||
import VideoPlayer from '$lib/components/VideoPlayer.svelte';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '$lib/components/avatar';
|
||||
import ProfilePicture from '$lib/components/ProfilePicture.svelte';
|
||||
|
||||
interface Find {
|
||||
id: string;
|
||||
@@ -112,14 +112,11 @@
|
||||
<SheetContent side={isMobile ? 'bottom' : 'right'} class="sheet-content">
|
||||
<SheetHeader class="sheet-header">
|
||||
<div class="user-section">
|
||||
<Avatar class="user-avatar">
|
||||
{#if find.user.profilePictureUrl}
|
||||
<AvatarImage src={find.user.profilePictureUrl} alt={find.user.username} />
|
||||
{/if}
|
||||
<AvatarFallback class="avatar-fallback">
|
||||
{find.user.username.slice(0, 2).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<ProfilePicture
|
||||
username={find.user.username}
|
||||
profilePictureUrl={find.user.profilePictureUrl}
|
||||
class="user-avatar"
|
||||
/>
|
||||
<div class="user-info">
|
||||
<SheetTitle class="find-title">{find.title}</SheetTitle>
|
||||
<div class="find-meta">
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger
|
||||
} from './dropdown-menu';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from './avatar';
|
||||
import { Skeleton } from './skeleton';
|
||||
import ProfilePicture from './ProfilePicture.svelte';
|
||||
import ProfilePictureSheet from './ProfilePictureSheet.svelte';
|
||||
|
||||
interface Props {
|
||||
@@ -22,9 +22,6 @@
|
||||
|
||||
let showProfilePictureSheet = $state(false);
|
||||
|
||||
// Get the first letter of username for avatar
|
||||
const initial = username.charAt(0).toUpperCase();
|
||||
|
||||
function openProfilePictureSheet() {
|
||||
showProfilePictureSheet = true;
|
||||
}
|
||||
@@ -36,18 +33,7 @@
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger class="profile-trigger">
|
||||
{#if loading}
|
||||
<Skeleton class="h-10 w-10 rounded-full" />
|
||||
{:else}
|
||||
<Avatar class="profile-avatar">
|
||||
{#if profilePictureUrl}
|
||||
<AvatarImage src={profilePictureUrl} alt={username} />
|
||||
{/if}
|
||||
<AvatarFallback class="profile-avatar-fallback">
|
||||
{initial}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{/if}
|
||||
<ProfilePicture {username} {profilePictureUrl} {loading} class="profile-avatar" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="end" class="profile-dropdown-content">
|
||||
@@ -144,15 +130,6 @@
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
:global(.profile-avatar-fallback) {
|
||||
background: black;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
:global(.profile-dropdown-content) {
|
||||
min-width: 200px;
|
||||
max-width: 240px;
|
||||
|
||||
59
src/lib/components/ProfilePicture.svelte
Normal file
59
src/lib/components/ProfilePicture.svelte
Normal file
@@ -0,0 +1,59 @@
|
||||
<script lang="ts">
|
||||
import { Avatar, AvatarFallback, AvatarImage } from './avatar';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
interface Props {
|
||||
username: string;
|
||||
profilePictureUrl?: string | null;
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
class?: string;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
username,
|
||||
profilePictureUrl,
|
||||
size = 'md',
|
||||
class: className,
|
||||
loading = false
|
||||
}: Props = $props();
|
||||
|
||||
const initial = username.charAt(0).toUpperCase();
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'h-8 w-8',
|
||||
md: 'h-10 w-10',
|
||||
lg: 'h-16 w-16',
|
||||
xl: 'h-24 w-24'
|
||||
};
|
||||
|
||||
const fallbackSizeClasses = {
|
||||
sm: 'text-xs',
|
||||
md: 'text-base',
|
||||
lg: 'text-xl',
|
||||
xl: 'text-3xl'
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<div class={cn('animate-pulse rounded-full bg-gray-200', sizeClasses[size], className)}></div>
|
||||
{:else}
|
||||
<Avatar class={cn(sizeClasses[size], className)}>
|
||||
{#if profilePictureUrl}
|
||||
<AvatarImage src={profilePictureUrl} alt={username} />
|
||||
{/if}
|
||||
<AvatarFallback class={cn('profile-picture-fallback', fallbackSizeClasses[size])}>
|
||||
{initial}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
:global(.profile-picture-fallback) {
|
||||
background: black;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from './sheet';
|
||||
import { Button } from './button';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from './avatar';
|
||||
import ProfilePicture from './ProfilePicture.svelte';
|
||||
|
||||
interface Props {
|
||||
userId: string;
|
||||
@@ -19,8 +19,6 @@
|
||||
let isDeleting = $state(false);
|
||||
let showModal = $state(true);
|
||||
|
||||
const initial = username.charAt(0).toUpperCase();
|
||||
|
||||
// Close modal when showModal changes to false
|
||||
$effect(() => {
|
||||
if (!showModal && onClose) {
|
||||
@@ -118,14 +116,7 @@
|
||||
|
||||
<div class="profile-picture-content">
|
||||
<div class="current-avatar">
|
||||
<Avatar class="large-avatar">
|
||||
{#if profilePictureUrl}
|
||||
<AvatarImage src={profilePictureUrl} alt={username} />
|
||||
{/if}
|
||||
<AvatarFallback class="large-avatar-fallback">
|
||||
{initial}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<ProfilePicture {username} {profilePictureUrl} size="xl" class="large-avatar" />
|
||||
</div>
|
||||
|
||||
<div class="upload-section">
|
||||
@@ -190,15 +181,6 @@
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
:global(.large-avatar-fallback) {
|
||||
background: black;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 48px;
|
||||
border: 4px solid #fff;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -3,6 +3,7 @@ export { default as Input } from './components/Input.svelte';
|
||||
export { default as Button } from './components/Button.svelte';
|
||||
export { default as ErrorMessage } from './components/ErrorMessage.svelte';
|
||||
export { default as ProfilePanel } from './components/ProfilePanel.svelte';
|
||||
export { default as ProfilePicture } from './components/ProfilePicture.svelte';
|
||||
export { default as ProfilePictureSheet } from './components/ProfilePictureSheet.svelte';
|
||||
export { default as Header } from './components/Header.svelte';
|
||||
export { default as Modal } from './components/Modal.svelte';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/card';
|
||||
import { Button } from '$lib/components/button';
|
||||
import { Input } from '$lib/components/input';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '$lib/components/avatar';
|
||||
import ProfilePicture from '$lib/components/ProfilePicture.svelte';
|
||||
import { Badge } from '$lib/components/badge';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import type { PageData } from './$types';
|
||||
@@ -184,10 +184,10 @@
|
||||
<div class="user-grid">
|
||||
{#each friends as friend (friend.id)}
|
||||
<div class="user-card">
|
||||
<Avatar>
|
||||
<AvatarImage src={friend.friendProfilePictureUrl} alt={friend.friendUsername} />
|
||||
<AvatarFallback>{friend.friendUsername.charAt(0).toUpperCase()}</AvatarFallback>
|
||||
</Avatar>
|
||||
<ProfilePicture
|
||||
username={friend.friendUsername}
|
||||
profilePictureUrl={friend.friendProfilePictureUrl}
|
||||
/>
|
||||
<div class="user-info">
|
||||
<span class="username">{friend.friendUsername}</span>
|
||||
<Badge variant="secondary">Friend</Badge>
|
||||
@@ -218,15 +218,10 @@
|
||||
<div class="user-grid">
|
||||
{#each receivedRequests as request (request.id)}
|
||||
<div class="user-card">
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src={request.friendProfilePictureUrl}
|
||||
alt={request.friendUsername}
|
||||
/>
|
||||
<AvatarFallback
|
||||
>{request.friendUsername.charAt(0).toUpperCase()}</AvatarFallback
|
||||
>
|
||||
</Avatar>
|
||||
<ProfilePicture
|
||||
username={request.friendUsername}
|
||||
profilePictureUrl={request.friendProfilePictureUrl}
|
||||
/>
|
||||
<div class="user-info">
|
||||
<span class="username">{request.friendUsername}</span>
|
||||
<Badge variant="outline">Pending</Badge>
|
||||
@@ -264,15 +259,10 @@
|
||||
<div class="user-grid">
|
||||
{#each sentRequests as request (request.id)}
|
||||
<div class="user-card">
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src={request.friendProfilePictureUrl}
|
||||
alt={request.friendUsername}
|
||||
/>
|
||||
<AvatarFallback
|
||||
>{request.friendUsername.charAt(0).toUpperCase()}</AvatarFallback
|
||||
>
|
||||
</Avatar>
|
||||
<ProfilePicture
|
||||
username={request.friendUsername}
|
||||
profilePictureUrl={request.friendProfilePictureUrl}
|
||||
/>
|
||||
<div class="user-info">
|
||||
<span class="username">{request.friendUsername}</span>
|
||||
<Badge variant="secondary">Sent</Badge>
|
||||
@@ -305,10 +295,10 @@
|
||||
<div class="user-grid">
|
||||
{#each searchResults as user (user.id)}
|
||||
<div class="user-card">
|
||||
<Avatar>
|
||||
<AvatarImage src={user.profilePictureUrl} alt={user.username} />
|
||||
<AvatarFallback>{user.username.charAt(0).toUpperCase()}</AvatarFallback>
|
||||
</Avatar>
|
||||
<ProfilePicture
|
||||
username={user.username}
|
||||
profilePictureUrl={user.profilePictureUrl}
|
||||
/>
|
||||
<div class="user-info">
|
||||
<span class="username">{user.username}</span>
|
||||
{#if user.friendshipStatus === 'accepted'}
|
||||
|
||||
Reference in New Issue
Block a user