408 lines
8.5 KiB
Vue
408 lines
8.5 KiB
Vue
<template>
|
|
<div
|
|
v-motion
|
|
:initial="{ opacity: 0, scale: 0.95 }"
|
|
:enter="{
|
|
opacity: 1,
|
|
scale: 1,
|
|
transition: {
|
|
delay: delay * 50,
|
|
type: 'spring',
|
|
stiffness: 200,
|
|
damping: 20
|
|
}
|
|
}"
|
|
:hovered="{ scale: 1.02 }"
|
|
class="member-card"
|
|
:class="[
|
|
`member-card--${variant}`,
|
|
{ 'member-card--featured': featured }
|
|
]"
|
|
@click="$emit('click', member)"
|
|
>
|
|
<div class="member-card__header">
|
|
<div class="member-card__avatar">
|
|
<img
|
|
v-if="member.avatar"
|
|
:src="member.avatar"
|
|
:alt="member.name"
|
|
@error="handleImageError"
|
|
/>
|
|
<div v-else class="member-card__avatar-placeholder">
|
|
{{ initials }}
|
|
</div>
|
|
<div
|
|
v-if="member.status === 'online'"
|
|
class="member-card__status-indicator"
|
|
/>
|
|
</div>
|
|
|
|
<div v-if="member.role" class="member-card__role">
|
|
{{ member.role }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="member-card__body">
|
|
<h3 class="member-card__name">{{ member.name }}</h3>
|
|
<p v-if="member.title" class="member-card__title">{{ member.title }}</p>
|
|
<p v-if="member.company" class="member-card__company">{{ member.company }}</p>
|
|
|
|
<div v-if="member.tags && member.tags.length" class="member-card__tags">
|
|
<span
|
|
v-for="tag in member.tags.slice(0, 3)"
|
|
:key="tag"
|
|
class="member-card__tag"
|
|
>
|
|
{{ tag }}
|
|
</span>
|
|
<span
|
|
v-if="member.tags.length > 3"
|
|
class="member-card__tag member-card__tag--more"
|
|
>
|
|
+{{ member.tags.length - 3 }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="member-card__footer">
|
|
<div class="member-card__stats">
|
|
<div v-if="member.joinDate" class="member-card__stat">
|
|
<span class="member-card__stat-label">Member Since</span>
|
|
<span class="member-card__stat-value">{{ member.joinDate }}</span>
|
|
</div>
|
|
<div v-if="member.connections !== undefined" class="member-card__stat">
|
|
<span class="member-card__stat-label">Connections</span>
|
|
<span class="member-card__stat-value">{{ member.connections }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="member-card__actions">
|
|
<button
|
|
class="member-card__action"
|
|
@click.stop="$emit('connect', member)"
|
|
>
|
|
<span>{{ member.connected ? '✓' : '+' }}</span>
|
|
{{ member.connected ? 'Connected' : 'Connect' }}
|
|
</button>
|
|
<button
|
|
class="member-card__action member-card__action--secondary"
|
|
@click.stop="$emit('message', member)"
|
|
>
|
|
<span>✉</span>
|
|
Message
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
|
|
interface Member {
|
|
id: string | number
|
|
name: string
|
|
avatar?: string
|
|
title?: string
|
|
company?: string
|
|
role?: string
|
|
status?: 'online' | 'offline' | 'away'
|
|
tags?: string[]
|
|
joinDate?: string
|
|
connections?: number
|
|
connected?: boolean
|
|
}
|
|
|
|
interface Props {
|
|
member: Member
|
|
variant?: 'glass' | 'solid' | 'outline'
|
|
featured?: boolean
|
|
delay?: number
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
variant: 'glass',
|
|
featured: false,
|
|
delay: 0
|
|
})
|
|
|
|
defineEmits<{
|
|
click: [member: Member]
|
|
connect: [member: Member]
|
|
message: [member: Member]
|
|
}>()
|
|
|
|
const initials = computed(() => {
|
|
const names = props.member.name.split(' ')
|
|
return names.map(n => n[0]).join('').toUpperCase().slice(0, 2)
|
|
})
|
|
|
|
const handleImageError = (e: Event) => {
|
|
const target = e.target as HTMLImageElement
|
|
target.style.display = 'none'
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.member-card {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 1.5rem;
|
|
border-radius: 16px;
|
|
cursor: pointer;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
// Glass variant
|
|
&--glass {
|
|
background: rgba(255, 255, 255, 0.7);
|
|
backdrop-filter: blur(20px);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
|
|
|
|
&:hover {
|
|
background: rgba(255, 255, 255, 0.8);
|
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
|
|
transform: translateY(-4px);
|
|
}
|
|
}
|
|
|
|
// Solid variant
|
|
&--solid {
|
|
background: white;
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
|
|
|
&:hover {
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
transform: translateY(-4px);
|
|
}
|
|
}
|
|
|
|
// Outline variant
|
|
&--outline {
|
|
background: transparent;
|
|
border: 2px solid rgba(220, 38, 38, 0.2);
|
|
|
|
&:hover {
|
|
background: rgba(220, 38, 38, 0.05);
|
|
border-color: rgba(220, 38, 38, 0.3);
|
|
transform: translateY(-4px);
|
|
}
|
|
}
|
|
|
|
// Featured state
|
|
&--featured {
|
|
border: 2px solid #dc2626;
|
|
box-shadow: 0 8px 32px rgba(220, 38, 38, 0.15);
|
|
|
|
&::before {
|
|
content: '⭐';
|
|
position: absolute;
|
|
top: -0.5rem;
|
|
right: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 2rem;
|
|
height: 2rem;
|
|
background: #dc2626;
|
|
border-radius: 50%;
|
|
font-size: 1rem;
|
|
}
|
|
}
|
|
|
|
&__header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
&__avatar {
|
|
position: relative;
|
|
width: 4rem;
|
|
height: 4rem;
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
|
|
|
|
img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
}
|
|
|
|
&__avatar-placeholder {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
height: 100%;
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
color: #dc2626;
|
|
background: linear-gradient(135deg,
|
|
rgba(220, 38, 38, 0.1) 0%,
|
|
rgba(220, 38, 38, 0.05) 100%);
|
|
}
|
|
|
|
&__status-indicator {
|
|
position: absolute;
|
|
bottom: 0;
|
|
right: 0;
|
|
width: 1rem;
|
|
height: 1rem;
|
|
background: #10b981;
|
|
border: 2px solid white;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
&__role {
|
|
padding: 0.25rem 0.75rem;
|
|
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
|
|
color: white;
|
|
border-radius: 20px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
&__body {
|
|
flex: 1;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
&__name {
|
|
margin: 0 0 0.25rem;
|
|
font-size: 1.125rem;
|
|
font-weight: 600;
|
|
color: #27272a;
|
|
}
|
|
|
|
&__title {
|
|
margin: 0 0 0.125rem;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
color: #dc2626;
|
|
}
|
|
|
|
&__company {
|
|
margin: 0 0 0.75rem;
|
|
font-size: 0.875rem;
|
|
color: #6b7280;
|
|
}
|
|
|
|
&__tags {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
&__tag {
|
|
padding: 0.25rem 0.5rem;
|
|
background: rgba(220, 38, 38, 0.1);
|
|
color: #dc2626;
|
|
border-radius: 6px;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
|
|
&--more {
|
|
background: rgba(107, 114, 128, 0.1);
|
|
color: #6b7280;
|
|
}
|
|
}
|
|
|
|
&__footer {
|
|
padding-top: 1rem;
|
|
border-top: 1px solid rgba(220, 38, 38, 0.1);
|
|
}
|
|
|
|
&__stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 1rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
&__stat {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
&__stat-label {
|
|
font-size: 0.75rem;
|
|
color: #6b7280;
|
|
}
|
|
|
|
&__stat-value {
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
color: #27272a;
|
|
}
|
|
|
|
&__actions {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
&__action {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.25rem;
|
|
padding: 0.5rem;
|
|
background: #dc2626;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
|
|
span {
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
&:hover {
|
|
background: #b91c1c;
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
&--secondary {
|
|
background: rgba(220, 38, 38, 0.1);
|
|
color: #dc2626;
|
|
|
|
&:hover {
|
|
background: rgba(220, 38, 38, 0.2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dark mode support
|
|
@media (prefers-color-scheme: dark) {
|
|
.member-card {
|
|
&--glass {
|
|
background: rgba(30, 30, 30, 0.7);
|
|
border-color: rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
&--solid {
|
|
background: #27272a;
|
|
}
|
|
|
|
&__name {
|
|
color: white;
|
|
}
|
|
|
|
&__stat-value {
|
|
color: #e5e5e5;
|
|
}
|
|
}
|
|
}
|
|
</style> |