Stop the profile header from flickering on scroll.
Use a sticky avatar pin instead of changing hero height while the white sheet moves. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -99,13 +99,6 @@ const unreadLabel = computed(() =>
|
||||
notificationStore.unreadCount > 9 ? '9+' : String(notificationStore.unreadCount),
|
||||
)
|
||||
const samplePrice = computed(() => sampleProduct.value.basePrice ?? SAMPLE_PRODUCT.basePrice ?? 0)
|
||||
const collapse = ref(0)
|
||||
const COLLAPSE_DISTANCE = 160
|
||||
|
||||
function onProfileScroll(event: Event) {
|
||||
const top = (event.target as HTMLElement).scrollTop
|
||||
collapse.value = Math.min(1, Math.max(0, top / COLLAPSE_DISTANCE))
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
@@ -134,59 +127,58 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<div class="page profile-page">
|
||||
<header class="hero" :class="{ 'is-collapsed': collapse > 0.85 }" :style="{ '--c': String(collapse) }">
|
||||
<div class="hero__top">
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn"
|
||||
aria-label="تنظیمات"
|
||||
@click="router.push('/profile/display')"
|
||||
>
|
||||
<IconSettings :size="20" color="#ffffff" />
|
||||
</button>
|
||||
<h1>پروفایل من</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn"
|
||||
aria-label="اعلانها"
|
||||
@click="router.push('/profile/notifications')"
|
||||
>
|
||||
<IconBell :size="20" color="#ffffff" />
|
||||
<span
|
||||
v-if="notificationStore.hasUnread"
|
||||
class="icon-btn__badge"
|
||||
:aria-label="`${notificationStore.unreadCount} اعلان خواندهنشده`"
|
||||
>
|
||||
{{ unreadLabel }}
|
||||
<div class="page__scroll page__scroll--with-nav profile-scroll">
|
||||
<header class="hero">
|
||||
<div class="hero__pin">
|
||||
<div class="hero__top">
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn"
|
||||
aria-label="تنظیمات"
|
||||
@click="router.push('/profile/display')"
|
||||
>
|
||||
<IconSettings :size="20" color="#ffffff" />
|
||||
</button>
|
||||
<h1>پروفایل من</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn"
|
||||
aria-label="اعلانها"
|
||||
@click="router.push('/profile/notifications')"
|
||||
>
|
||||
<IconBell :size="20" color="#ffffff" />
|
||||
<span
|
||||
v-if="notificationStore.hasUnread"
|
||||
class="icon-btn__badge"
|
||||
:aria-label="`${notificationStore.unreadCount} اعلان خواندهنشده`"
|
||||
>
|
||||
{{ unreadLabel }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<UserAvatar
|
||||
class="hero__avatar"
|
||||
:src="auth.user?.avatarUrl"
|
||||
:initials="displayName.charAt(0)"
|
||||
:size="84"
|
||||
/>
|
||||
</div>
|
||||
<div class="hero__meta">
|
||||
<h2>{{ displayName }}</h2>
|
||||
<p>کد واسطه: {{ code }}</p>
|
||||
<span class="level-pill">
|
||||
<svg class="level-pill__crown" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M3.5 17.5h17l-1.2-8.2a1 1 0 0 0-1.5-.7L14 11l-2.5-5.2a1 1 0 0 0-1.8 0L7.2 11 4.2 8.6a1 1 0 0 0-1.5.7L3.5 17.5Zm1.7 1.5h13.6v1.2H5.2V19Z"
|
||||
/>
|
||||
</svg>
|
||||
سطح: {{ levelLabel }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<UserAvatar
|
||||
class="hero__avatar"
|
||||
:src="auth.user?.avatarUrl"
|
||||
:initials="displayName.charAt(0)"
|
||||
:size="84"
|
||||
/>
|
||||
<div class="hero__meta">
|
||||
<h2>{{ displayName }}</h2>
|
||||
<p>کد واسطه: {{ code }}</p>
|
||||
<span class="level-pill">
|
||||
<svg class="level-pill__crown" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M3.5 17.5h17l-1.2-8.2a1 1 0 0 0-1.5-.7L14 11l-2.5-5.2a1 1 0 0 0-1.8 0L7.2 11 4.2 8.6a1 1 0 0 0-1.5.7L3.5 17.5Zm1.7 1.5h13.6v1.2H5.2V19Z"
|
||||
/>
|
||||
</svg>
|
||||
سطح: {{ levelLabel }}
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div
|
||||
class="page__scroll page__scroll--with-nav profile-scroll"
|
||||
@scroll.passive="onProfileScroll"
|
||||
>
|
||||
<div class="profile-sheet">
|
||||
<PageSkeleton v-if="loading" variant="profile" />
|
||||
<template v-else>
|
||||
<section class="panel">
|
||||
@@ -332,6 +324,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -342,30 +335,39 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.hero {
|
||||
--c: 0;
|
||||
flex-shrink: 0;
|
||||
color: #fff;
|
||||
padding: 0 0 28px;
|
||||
text-align: center;
|
||||
background: linear-gradient(
|
||||
145deg,
|
||||
var(--color-primary) 0%,
|
||||
color-mix(in srgb, var(--color-primary) 78%, #0a1f18) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.hero__pin {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
padding: 12px var(--page-padding) 10px;
|
||||
background: linear-gradient(
|
||||
145deg,
|
||||
var(--color-primary) 0%,
|
||||
color-mix(in srgb, var(--color-primary) 78%, #0a1f18) 100%
|
||||
);
|
||||
color: #fff;
|
||||
padding: 12px var(--page-padding) calc(44px - var(--c) * 34px);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero__top {
|
||||
display: grid;
|
||||
grid-template-columns: 40px 1fr 40px;
|
||||
align-items: center;
|
||||
margin-bottom: calc(16px * (1 - var(--c)));
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.hero__top h1 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
opacity: calc(1 - var(--c));
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
@@ -397,23 +399,13 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.hero__avatar {
|
||||
margin: 0 auto calc(12px * (1 - var(--c)));
|
||||
margin: 0 auto;
|
||||
border: 3px solid rgba(255, 255, 255, 0.35);
|
||||
color: #fff;
|
||||
transform: scale(calc(1 - var(--c) * 0.18));
|
||||
transform-origin: center top;
|
||||
}
|
||||
|
||||
.hero__meta {
|
||||
overflow: hidden;
|
||||
max-height: calc((1 - var(--c)) * 130px);
|
||||
opacity: calc(1 - var(--c));
|
||||
transform: translateY(calc(var(--c) * -12px));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hero.is-collapsed .hero__meta {
|
||||
visibility: hidden;
|
||||
padding: 10px var(--page-padding) 0;
|
||||
}
|
||||
|
||||
.hero h2 {
|
||||
@@ -447,11 +439,16 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.profile-scroll {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
margin-top: -24px;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.profile-sheet {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: -20px;
|
||||
padding: 20px var(--page-padding) 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -461,7 +458,7 @@ onMounted(async () => {
|
||||
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.profile-scroll > * {
|
||||
.profile-sheet > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user