mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-21 21:33:18 +01:00
118 lines
3.2 KiB
Vue
118 lines
3.2 KiB
Vue
<template>
|
|
<section :class="{ 'h-screen min-h-0': scrollable }" class="flex flex-col">
|
|
<header
|
|
v-if="$slots.header"
|
|
class="sticky top-[70px] z-[2] border-b border-base-content/10 bg-base py-2 shadow-[1px_1px_2px_0_rgb(0,0,0,0.05)] md:top-0"
|
|
>
|
|
<slot name="header"></slot>
|
|
</header>
|
|
<main :data-scrolling="scrollable ? true : undefined" class="snap-y overflow-auto">
|
|
<div class="invisible relative md:visible" v-show="scrollContext.paused">
|
|
<div class="absolute right-44 top-4">
|
|
<ScrollProgress
|
|
:indeterminate="loadingMore"
|
|
:auto-hide="!loadingMore"
|
|
:progress="scrollContext.progress"
|
|
:date="scrollContext.currentDate"
|
|
class="!fixed z-10 min-w-40"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div ref="scrollableContent">
|
|
<slot></slot>
|
|
<div v-if="scrollContext.loading" class="m-4 text-center">
|
|
<span class="loading loading-ring loading-md text-primary"></span>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="animate-background h-1 bg-gradient-to-br from-primary via-transparent to-primary"
|
|
v-show="!scrollContext.paused && !scrollContext.loading"
|
|
></div>
|
|
<div ref="scrollObserver" class="h-px"></div>
|
|
</main>
|
|
|
|
<div class="mr-16 text-right">
|
|
<transition name="fade">
|
|
<button
|
|
class="transition-colorsblur-xs dark btn btn-primary fixed bottom-8 rounded p-3 text-primary-content shadow"
|
|
:class="hasMore ? 'btn-secondary animate-bounce-fast text-secondary-content' : ''"
|
|
@click="scrollToBottom()"
|
|
v-show="scrollContext.paused"
|
|
>
|
|
<mdi:chevron-double-down />
|
|
</button>
|
|
</transition>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
const { scrollable = false } = defineProps<{ scrollable?: boolean }>();
|
|
|
|
let hasMore = ref(false);
|
|
const scrollObserver = ref<HTMLElement>();
|
|
const scrollableContent = ref<HTMLElement>();
|
|
|
|
const scrollContext = provideScrollContext();
|
|
|
|
const { loadingMore } = useLoggingContext();
|
|
|
|
useIntersectionObserver(scrollObserver, ([entry]) => (scrollContext.paused = entry.intersectionRatio == 0), {
|
|
threshold: [0, 1],
|
|
rootMargin: "80px 0px",
|
|
});
|
|
|
|
useMutationObserver(
|
|
scrollableContent,
|
|
(records) => {
|
|
if (!scrollContext.paused) {
|
|
scrollToBottom();
|
|
} else {
|
|
const record = records[records.length - 1];
|
|
const children = (record.target as HTMLElement).children;
|
|
if (children[children.length - 1] == record.addedNodes[record.addedNodes.length - 1]) {
|
|
hasMore.value = true;
|
|
}
|
|
}
|
|
},
|
|
{ childList: true, subtree: true },
|
|
);
|
|
|
|
function scrollToBottom(behavior: "auto" | "smooth" = "auto") {
|
|
scrollObserver.value?.scrollIntoView({ behavior });
|
|
hasMore.value = false;
|
|
}
|
|
</script>
|
|
<style scoped lang="postcss">
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
@apply transition-opacity;
|
|
}
|
|
|
|
.fade-enter-from,
|
|
.fade-leave-to {
|
|
@apply opacity-0;
|
|
}
|
|
|
|
.animate-background {
|
|
background-size: 400% 400%;
|
|
animation: gradient-animation 4s ease infinite;
|
|
}
|
|
|
|
@keyframes gradient-animation {
|
|
0%,
|
|
100% {
|
|
background-position: 0% 0%;
|
|
}
|
|
50% {
|
|
background-position: 100% 100%;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<style>
|
|
.splitpanes__pane {
|
|
overflow: unset !important;
|
|
}
|
|
</style>
|