mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 08:28:20 +01:00
feat: ✨ Add popup profile preview when hovering on note avatars
This commit is contained in:
parent
4e370cd056
commit
80b1fc87f7
|
|
@ -23,9 +23,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex flex-row">
|
<div v-else class="flex flex-row">
|
||||||
<NuxtLink :href="accountUrl" class="shrink-0">
|
<NuxtLink :href="accountUrl" class="shrink-0">
|
||||||
<Avatar :src="note?.account.avatar" :alt="`${note?.account.acct}'s avatar`"
|
<UserCard :account="note?.account">
|
||||||
class="h-12 w-12 rounded ring-1 ring-white/5" />
|
<Avatar :src="note?.account.avatar" :alt="`${note?.account.acct}'s avatar`"
|
||||||
<span class="sr-only">Account profile</span>
|
class="h-12 w-12 rounded ring-1 ring-white/5" />
|
||||||
|
<span class="sr-only">Account profile</span>
|
||||||
|
</UserCard>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<div class="flex flex-col items-start justify-around ml-4 grow overflow-hidden">
|
<div class="flex flex-col items-start justify-around ml-4 grow overflow-hidden">
|
||||||
<div class="flex flex-row items-center justify-between w-full">
|
<div class="flex flex-row items-center justify-between w-full">
|
||||||
|
|
@ -69,6 +71,7 @@
|
||||||
import type { Status } from "@lysand-org/client/types";
|
import type { Status } from "@lysand-org/client/types";
|
||||||
import Avatar from "~/components/avatars/avatar.vue";
|
import Avatar from "~/components/avatars/avatar.vue";
|
||||||
import Skeleton from "~/components/skeleton/Skeleton.vue";
|
import Skeleton from "~/components/skeleton/Skeleton.vue";
|
||||||
|
import UserCard from "~/components/social-elements/users/UserCard.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note?: Status;
|
note?: Status;
|
||||||
|
|
|
||||||
118
components/social-elements/users/UserCard.vue
Normal file
118
components/social-elements/users/UserCard.vue
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
<template>
|
||||||
|
<HoverCard.Root :positioning="{
|
||||||
|
placement: 'bottom',
|
||||||
|
}" v-if="isEnabled.value">
|
||||||
|
<HoverCard.Trigger :as-child="true">
|
||||||
|
<slot />
|
||||||
|
</HoverCard.Trigger>
|
||||||
|
<Teleport to="body" v-if="account">
|
||||||
|
<HoverCard.Positioner>
|
||||||
|
<HoverCard.Content
|
||||||
|
class="bg-dark-700 w-96 overflow-y-auto rounded ring-1 ring-white/10 max-h-[60vh] text-sm">
|
||||||
|
<Avatar :src="account.header" :alt="`${account.acct}'s header image'`"
|
||||||
|
class="w-full aspect-[8/3] border-b border-white/10 bg-dark-700 !rounded-none" />
|
||||||
|
|
||||||
|
<div class="flex items-start justify-between px-4 py-3">
|
||||||
|
<Avatar :src="account.avatar" :alt="`${account.acct}'s avatar'`"
|
||||||
|
class="h-32 w-32 -mt-[4.5rem] z-10 shrink-0 rounded ring-2 ring-dark-200" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2 px-4">
|
||||||
|
<h2
|
||||||
|
class="text-xl font-bold text-gray-100 tracking-tight bg-gradient-to-r from-primary-300 via-purple-300 to-indigo-400 text-transparent bg-clip-text">
|
||||||
|
<span v-html="display_name"></span>
|
||||||
|
<iconify-icon v-if="account.locked" icon="tabler:lock" width="1.25rem" height="1.25rem"
|
||||||
|
class="text-gray-400 cursor-pointer align-text-top"
|
||||||
|
title="This account manually approves its followers" />
|
||||||
|
</h2>
|
||||||
|
<span class="text-gray-300 block mt-2">
|
||||||
|
@{{ account.acct }}
|
||||||
|
</span>
|
||||||
|
<div class="flex flex-row flex-wrap gap-4 mt-4" v-if="isDeveloper || visibleRoles.length > 0">
|
||||||
|
<Badge v-for="role of visibleRoles" :key="role.id" :name="role.name"
|
||||||
|
:description="role.description" :img="role.icon" />
|
||||||
|
<Badge v-if="isDeveloper" name="Lysand Developer"
|
||||||
|
description="This user is a Lysand developer." :verified="true" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 px-4">
|
||||||
|
<div class="prose prose-invert prose-sm" v-html="note"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 flex items-center space-x-4 px-4">
|
||||||
|
<div class="flex items-center space-x-1">
|
||||||
|
<iconify-icon icon="tabler:calendar" width="1.25rem" height="1.25rem"
|
||||||
|
class="text-gray-400" />
|
||||||
|
<span class="text-gray-400">Created {{ formattedJoin }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="account.bot" class="flex items-center space-x-1">
|
||||||
|
<iconify-icon icon="tabler:robot" width="1.25rem" height="1.25rem" class="text-gray-400" />
|
||||||
|
<span class="text-gray-400">Bot</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 flex items-center space-x-4 px-4">
|
||||||
|
<div class="cursor-pointer hover:underline space-x-1">
|
||||||
|
<span class="font-bold text-gray-200">{{ account.statuses_count }}</span>
|
||||||
|
<span class="text-gray-400">Posts</span>
|
||||||
|
</div>
|
||||||
|
<div class="cursor-pointer hover:underline space-x-1">
|
||||||
|
<span class="font-bold text-gray-200">{{ account.following_count }}</span>
|
||||||
|
<span class="text-gray-400">Following</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="fields && fields.length > 0" class="mt-4 px-4 flex-col flex space-y-3">
|
||||||
|
<div v-for="field of fields" :key="field.name" class="flex flex-col gap-1">
|
||||||
|
<span class="text-primary-500 font-semibold" v-html="field.name"></span>
|
||||||
|
<span class="text-gray-200 prose prose-invert prose-sm break-all"
|
||||||
|
v-html="field.value"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HoverCard.Content>
|
||||||
|
</HoverCard.Positioner>
|
||||||
|
</Teleport>
|
||||||
|
</HoverCard.Root>
|
||||||
|
<slot v-else />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { HoverCard } from "@ark-ui/vue";
|
||||||
|
import type { Account } from "@lysand-org/client/types";
|
||||||
|
import Avatar from "~/components/avatars/avatar.vue";
|
||||||
|
import { SettingIds } from "~/settings";
|
||||||
|
import Badge from "./Badge.vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
account?: Account;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const config = useConfig();
|
||||||
|
const isEnabled = useSetting(SettingIds.PopupAvatarHover);
|
||||||
|
|
||||||
|
const formattedJoin = computed(() =>
|
||||||
|
Intl.DateTimeFormat("en-US", {
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
}).format(new Date(props.account?.created_at ?? 0)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handle = computed(() => {
|
||||||
|
if (!props.account?.acct.includes("@")) {
|
||||||
|
return `${props.account?.acct}@${new URL(useBaseUrl().value).host}`;
|
||||||
|
}
|
||||||
|
return props.account?.acct;
|
||||||
|
});
|
||||||
|
const isDeveloper = computed(() =>
|
||||||
|
config.DEVELOPER_HANDLES.includes(handle.value),
|
||||||
|
);
|
||||||
|
const visibleRoles = computed(
|
||||||
|
() => props.account?.roles.filter((r) => r.visible) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { display_name, fields, note } = useParsedAccount(
|
||||||
|
computed(() => props.account),
|
||||||
|
settings,
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
@ -123,7 +123,6 @@ export const settings = [
|
||||||
type: SettingType.Boolean,
|
type: SettingType.Boolean,
|
||||||
value: true,
|
value: true,
|
||||||
path: SettingPages.Behaviour,
|
path: SettingPages.Behaviour,
|
||||||
notImplemented: true,
|
|
||||||
} as Setting<SettingType.Boolean>,
|
} as Setting<SettingType.Boolean>,
|
||||||
{
|
{
|
||||||
id: SettingIds.InfiniteScroll,
|
id: SettingIds.InfiniteScroll,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue