Coves frontend - a photon fork
1<script lang="ts">
2 import { PostBody } from '$lib/feature/post'
3 import { Material } from 'mono-svelte'
4 import type { ClassValue } from 'svelte/elements'
5 import LabelStat from '../info/LabelStat.svelte'
6 import TextProps from '../text/TextProps.svelte'
7 import Avatar from './Avatar.svelte'
8 import Blobs from './Blobs.svelte'
9
10 interface Props {
11 avatar?: string
12 name: string
13 bio?: string
14 banner?: string | null
15 url?: string
16 stats?: {
17 name: string
18 value: string | number
19 format?: boolean
20 }[]
21 class?: ClassValue
22 nameDetail?: import('svelte').Snippet
23 actions?: import('svelte').Snippet
24 children?: import('svelte').Snippet
25 compact?: 'always' | 'lg'
26 avatarCircle?: boolean
27 }
28
29 let {
30 avatar,
31 name,
32 bio,
33 banner,
34 url,
35 stats = [],
36 class: clazz = '',
37 nameDetail,
38 actions,
39 children,
40 compact,
41 avatarCircle = true,
42 ...rest
43 }: Props = $props()
44</script>
45
46<div {...rest} class={['z-10 text-sm w-full space-y-4 @container', clazz]}>
47 <Material padding="xl" rounding="3xl" class="flex flex-col gap-2 @lg:gap-4">
48 {#if banner !== null}
49 <div
50 class="relative overflow-hidden rounded-t-[inherit] -m-6 mask-b-from-0 h-32 @lg:h-48"
51 >
52 {#if banner}
53 <img
54 src={banner}
55 class="w-full object-cover h-full bg-white dark:bg-zinc-900"
56 height="192"
57 alt="User banner"
58 />
59 {:else}
60 <div class="scale-150 h-full">
61 <Blobs seed={name} />
62 </div>
63 {/if}
64 </div>
65 {/if}
66
67 {#snippet pfp(width: number)}
68 <Avatar
69 {width}
70 url={avatar}
71 alt={name}
72 circle={avatarCircle}
73 class={[
74 'relative',
75 banner !== null && '-mt-4 @md:-mt-8',
76 !avatarCircle && 'rounded-xl @md:rounded-3xl!',
77 ]}
78 />
79 {/snippet}
80
81 <div class="contents @md:hidden">
82 {@render pfp(48)}
83 </div>
84
85 <div class="hidden @md:contents">
86 {@render pfp(72)}
87 </div>
88
89 <div class="space-y-1">
90 <svelte:element
91 this={url ? 'a' : 'h1'}
92 href={url}
93 class={[
94 'text-xl @md:text-2xl font-medium tracking-tight',
95 url &&
96 'hover:underline hover:text-primary-900 dark:hover:text-primary-100',
97 ]}
98 >
99 {name}
100 </svelte:element>
101 {#if nameDetail}
102 <p
103 class="flex items-center gap-0 text-sm text-slate-600 dark:text-zinc-400 max-w-full w-max"
104 >
105 <TextProps wrap="no-wrap">
106 {@render nameDetail?.()}
107 </TextProps>
108 </p>
109 {/if}
110 </div>
111 </Material>
112 {#if actions || stats.length > 0}
113 <div class="space-y-4">
114 <div class="flex flex-col flex-1">
115 {#if actions}
116 {@render actions?.()}
117 {/if}
118 </div>
119 {#if stats.length > 0}
120 <div
121 class={[
122 compact == 'lg' && 'lg:hidden',
123 compact == 'always' ? 'hidden' : 'flex',
124 'text-sm flex-row flex-wrap overflow-hidden gap-4 h-full',
125 ]}
126 >
127 {#each stats as stat}
128 <LabelStat
129 label={stat.name}
130 content={stat.value}
131 labelClass="text-sm"
132 contentClass="text-lg"
133 />
134 {/each}
135 </div>
136 {/if}
137 </div>
138 {/if}
139 {#if bio}
140 <PostBody
141 body={bio}
142 class={[
143 compact == 'lg' && 'lg:hidden',
144 compact == 'always' && 'hidden',
145 'relative',
146 ]}
147 clickThrough={false}
148 />
149 {/if}
150 {#if children}
151 <div class="space-y-3 py-4 pt-0 mt-4">
152 {@render children?.()}
153 </div>
154 {/if}
155</div>
156
157<style>
158 .banner-mask {
159 mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));
160 }
161</style>