|
|
|
@@ -2,7 +2,7 @@
|
|
|
|
|
<div
|
|
|
|
|
ref="headerRef"
|
|
|
|
|
class="app-header px-4 py-2 top-0 z-30 wrap-fix background-color-transition"
|
|
|
|
|
:class="{ 'app-header--scrolled': scrollY > 50 }"
|
|
|
|
|
:class="{ 'app-header--scrolled': isHeaderScrolled }"
|
|
|
|
|
>
|
|
|
|
|
<div class="flex justify-between my-2 items-center">
|
|
|
|
|
<div v-if="isBoursecuProject()" class="flex items-center gap-2">
|
|
|
|
@@ -40,47 +40,126 @@
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
|
|
|
|
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
|
|
|
|
import { useUserStore } from '@/store/userStore'
|
|
|
|
|
import router from '@/router'
|
|
|
|
|
import Icon from '@/components/BasicPacks/BasicIcon.vue'
|
|
|
|
|
import { getProjectMobileNavLogo, isBoursecuProject } from '@/utils/config'
|
|
|
|
|
|
|
|
|
|
const headerRef = ref<HTMLElement | null>(null)
|
|
|
|
|
const scrollY = ref(0)
|
|
|
|
|
const isHeaderScrolled = ref(false)
|
|
|
|
|
|
|
|
|
|
const userStore = useUserStore()
|
|
|
|
|
const projectLogo = computed(() => getProjectMobileNavLogo())
|
|
|
|
|
|
|
|
|
|
let intersectionObserver: IntersectionObserver | null = null
|
|
|
|
|
let scrollTarget: HTMLElement | Window | null = null
|
|
|
|
|
|
|
|
|
|
function isScrollableOverflow(value: string) {
|
|
|
|
|
return value === 'auto' || value === 'scroll' || value === 'overlay'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function findScrollParent(el: HTMLElement | null): HTMLElement | null {
|
|
|
|
|
let node = el?.parentElement ?? null
|
|
|
|
|
|
|
|
|
|
while (node) {
|
|
|
|
|
const { overflowY } = getComputedStyle(node)
|
|
|
|
|
if (overflowY === 'auto' || overflowY === 'scroll') {
|
|
|
|
|
const style = getComputedStyle(node)
|
|
|
|
|
const overflowY = style.overflowY
|
|
|
|
|
const overflow = style.overflow
|
|
|
|
|
|
|
|
|
|
if (isScrollableOverflow(overflowY) || isScrollableOverflow(overflow)) {
|
|
|
|
|
return node
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
node = node.parentElement
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateScrollY() {
|
|
|
|
|
if (!scrollTarget) return
|
|
|
|
|
scrollY.value = scrollTarget instanceof Window ? scrollTarget.scrollY : scrollTarget.scrollTop
|
|
|
|
|
function resolveScrollRoot(): HTMLElement | null {
|
|
|
|
|
const header = headerRef.value
|
|
|
|
|
if (!header) return null
|
|
|
|
|
|
|
|
|
|
const chromeInner = header.closest('.main-with-bottom-chrome-inner')
|
|
|
|
|
if (chromeInner instanceof HTMLElement) {
|
|
|
|
|
return chromeInner
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fromParent = findScrollParent(header)
|
|
|
|
|
if (fromParent) {
|
|
|
|
|
return fromParent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const global = document.querySelector('.main-with-bottom-chrome-inner')
|
|
|
|
|
return global instanceof HTMLElement ? global : null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
scrollTarget = findScrollParent(headerRef.value) ?? window
|
|
|
|
|
scrollTarget.addEventListener('scroll', updateScrollY, { passive: true })
|
|
|
|
|
updateScrollY()
|
|
|
|
|
function resolveScrollSentinel(scrollRoot: HTMLElement | null): HTMLElement | null {
|
|
|
|
|
if (!scrollRoot) return null
|
|
|
|
|
|
|
|
|
|
const local = scrollRoot.querySelector('.app-header-scroll-sentinel')
|
|
|
|
|
if (local instanceof HTMLElement) {
|
|
|
|
|
return local
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateScrollStateFromPosition() {
|
|
|
|
|
if (!scrollTarget) return
|
|
|
|
|
|
|
|
|
|
const next =
|
|
|
|
|
scrollTarget instanceof Window
|
|
|
|
|
? scrollTarget.scrollY > 50
|
|
|
|
|
: scrollTarget.scrollTop > 50
|
|
|
|
|
|
|
|
|
|
isHeaderScrolled.value = next
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function teardownScrollTracking() {
|
|
|
|
|
intersectionObserver?.disconnect()
|
|
|
|
|
intersectionObserver = null
|
|
|
|
|
|
|
|
|
|
scrollTarget?.removeEventListener('scroll', updateScrollStateFromPosition)
|
|
|
|
|
scrollTarget = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setupScrollTracking() {
|
|
|
|
|
teardownScrollTracking()
|
|
|
|
|
|
|
|
|
|
const scrollRoot = resolveScrollRoot()
|
|
|
|
|
const sentinel = resolveScrollSentinel(scrollRoot)
|
|
|
|
|
|
|
|
|
|
if (scrollRoot && sentinel && typeof IntersectionObserver !== 'undefined') {
|
|
|
|
|
intersectionObserver = new IntersectionObserver(
|
|
|
|
|
([entry]) => {
|
|
|
|
|
isHeaderScrolled.value = !entry?.isIntersecting
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
root: scrollRoot,
|
|
|
|
|
threshold: 0,
|
|
|
|
|
rootMargin: '-1px 0px 0px 0px',
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
intersectionObserver.observe(sentinel)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scrollTarget = scrollRoot ?? window
|
|
|
|
|
scrollTarget.addEventListener('scroll', updateScrollStateFromPosition, { passive: true })
|
|
|
|
|
updateScrollStateFromPosition()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
setupScrollTracking()
|
|
|
|
|
await nextTick()
|
|
|
|
|
setupScrollTracking()
|
|
|
|
|
requestAnimationFrame(() => setupScrollTracking())
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
scrollTarget?.removeEventListener('scroll', updateScrollY)
|
|
|
|
|
teardownScrollTracking()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
defineEmits<{
|
|
|
|
@@ -93,10 +172,8 @@ defineEmits<{
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* عین bg-white shadow opacity-95 قبلی */
|
|
|
|
|
.app-header--scrolled {
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
opacity: 0.95;
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.92);
|
|
|
|
|
box-shadow:
|
|
|
|
|
0 1px 3px 0 rgb(0 0 0 / 0.1),
|
|
|
|
|
0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
|
|
|