Bilibili-Evolved/registry/lib/components/style/home-redesign/fresh/VideoList.vue

133 lines
3.3 KiB
Vue

<template>
<div
class="fresh-home-video-list scroll-top scroll-bottom"
:class="{ 'not-empty': videos.length > 0 }"
>
<div ref="content" class="fresh-home-video-list-content">
<div v-if="videos.length === 0" class="fresh-home-video-list-empty">
<VLoading v-if="loading" />
<VEmpty v-else />
</div>
<VideoCardWrapper v-for="video of videos" v-else ref="cards" :key="video.id" :data="video" />
</div>
</div>
</template>
<script lang="ts">
import type { Ref, PropType } from 'vue'
import { defineComponent, ref } from 'vue'
import type { VideoCard } from '@/components/feeds/video-card'
import { enableHorizontalScroll } from '@/core/horizontal-scroll'
import { addComponentListener } from '@/core/settings'
import { VEmpty, VLoading } from '@/ui'
import { cleanUpScrollMask, setupScrollMask } from './scroll-mask'
import VideoCardWrapper from './VideoCardWrapper.vue'
export default defineComponent({
components: {
VEmpty,
VLoading,
VideoCardWrapper,
},
props: {
videos: {
type: Array as PropType<VideoCard[]>,
default: () => [],
},
loading: {
type: Boolean,
default: true,
},
},
setup: () => ({
content: ref(null) as Ref<HTMLDivElement | null>,
cards: ref(null) as Ref<InstanceType<typeof VideoCardWrapper>[] | null>,
}),
watch: {
videos: {
handler() {
this.setupIntersection()
},
deep: true,
},
loaded(value) {
if (value) {
this.setupIntersection()
}
},
},
beforeUnmount() {
cleanUpScrollMask(this.$el)
},
mounted() {
const container = this.content as HTMLElement
let cancel: () => void
addComponentListener(
'freshHome.horizontalWheelScroll',
(scroll: boolean) => {
if (scroll) {
cancel = enableHorizontalScroll(container)
} else {
cancel?.()
}
},
true,
)
},
methods: {
async setupIntersection() {
await this.$nextTick()
setupScrollMask({
container: this.$el,
items: this.cards.map(c => c.$el),
})
},
offsetPage(offset: number) {
const container = this.content as HTMLElement
const style = getComputedStyle(container)
const containerWidth = container.clientWidth
const wrapperWidth =
parseFloat(style.getPropertyValue('--card-width')) +
parseFloat(style.getPropertyValue('--card-padding'))
const pageWidth = Math.trunc(containerWidth / wrapperWidth) * wrapperWidth
container.scrollBy(offset * pageWidth, 0)
},
},
})
</script>
<style lang="scss">
@import 'common';
@import 'effects';
.fresh-home-video-list {
--card-height: var(--home-content-height);
--card-width: 200px;
--card-padding: 12px;
position: relative;
display: flex;
flex: 1 0 0;
width: 0;
@include scroll-mask-x(36px, var(--home-base-color));
&-content {
@include h-center();
@include no-scrollbar();
overscroll-behavior: initial;
flex: 1;
min-height: calc(var(--home-content-height) + var(--card-padding) * 2);
}
&-empty {
margin: var(--card-padding);
border: 2px dashed #8884;
border-radius: var(--home-card-radius);
flex-grow: 1;
align-self: stretch;
@include h-center();
}
&.not-empty &-content {
scroll-snap-type: x mandatory;
}
}
</style>