~james/core

core/web/src/lib/components/profile/FollowCardContent.svelte -rw-r--r-- 3.0 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
<script lang="ts">
	import { resolve } from "$app/paths";
	import Avatar from "#lib/components/ui/Avatar.svelte";
	import Users from "$icon/users";
	import FollowButton from "#lib/components/profile/FollowButton.svelte";
	import type { FollowChange, PersonData } from "#lib/components/profile/types.ts";
	import { createOptimisticCount } from "#lib/optimistic.svelte.ts";
	import Separator from "#lib/components/ui/Separator.svelte";
	import { formatCount, pluralize } from "#lib/format.ts";
	import { profileIdentifier } from "#lib/api/identity.ts";

	let {
		person,
		onFollow,
		followButton = true
	}: {
		person: PersonData;
		onFollow?: (change: FollowChange) => void;
		followButton?: boolean;
	} = $props();

	const identifier = $derived(profileIdentifier(person.handle, person.did));
	const profilePath = $derived(`/${identifier}`);

	const followerCount = createOptimisticCount({
		key: () => person.did,
		loaded: () => person.followers
	});

	const followed = (change: FollowChange) => {
		followerCount.adjust(change.delta);
		onFollow?.(change);
	};
</script>

<div
	data-testid="person"
	data-handle={person.handle}
	data-followers={followerCount.value}
	data-following={person.following}
	class="flex flex-col gap-md @compact:flex-row @compact:items-start @compact:justify-between"
>
	<div class="flex min-w-0 flex-1 items-center gap-md">
		<a
			href={resolve(profilePath as "/")}
			tabindex="-1"
			aria-hidden="true"
			class="flex size-24 shrink-0 items-center justify-center"
		>
			<Avatar
				did={person.did}
				avatarCid={person.avatarCid}
				handle={person.handle}
				size="full"
				pixels={96}
			/>
		</a>

		<div class="flex min-w-0 flex-1 flex-col justify-around">
			<div class="flex max-w-full items-center gap-xs">
				<a
					href={resolve(profilePath as "/")}
					class="max-w-full shrink-0 truncate font-semibold text-foreground-default hover:underline"
				>
					{identifier}
				</a>
				{#if person.pronouns}
					<span class="min-w-0 truncate text-foreground-muted" title={person.pronouns}
						>{person.pronouns}</span
					>
				{/if}
			</div>
			{#if person.description}
				<p class="mb-xs line-clamp-1 wrap-break-word" title={person.description}>
					{person.description}
				</p>
			{/if}
			<div class="my-xs flex max-w-full items-center gap-xs truncate text-foreground-subtle">
				<Users class="size-3.5 shrink-0" />
				<span
					><a
						href={resolve(`${profilePath}?tab=followers` as "/")}
						class="hover:text-foreground-default hover:underline"
						>{pluralize(followerCount.value, "follower", "followers")}</a
					></span
				>
				<Separator variant="dot" />
				<span
					><a
						href={resolve(`${profilePath}?tab=following` as "/")}
						class="hover:text-foreground-default hover:underline"
						>{formatCount(person.following)} following</a
					></span
				>
			</div>
		</div>
	</div>

	{#if !person.isSelf && followButton}
		<div class="w-full @compact:w-auto @compact:max-w-32">
			<FollowButton
				profileDid={person.did}
				initialRkey={person.viewerFollowRkey}
				onCommit={followed}
			/>
		</div>
	{/if}
</div>