Skip to content

Commit 8f3cd5d

Browse files
committed
feat(calendar): adds infinite scroll
1 parent 09f82f8 commit 8f3cd5d

9 files changed

+182
-126
lines changed

src/components/common/list/ListItem.vue

+25-30
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import {
1212
watch,
1313
} from 'vue';
1414
15-
import type { ListScrollItem } from '~/components/common/list/ListScroll.model';
16-
1715
import PosterPlaceholder from '~/assets/images/poster-placholder.webp';
1816
import ListItemPanel from '~/components/common/list/ListItemPanel.vue';
17+
import {
18+
type ListScrollItem,
19+
ListScrollItemType,
20+
} from '~/components/common/list/ListScroll.model';
1921
2022
import { useImageStore } from '~/stores/data/image.store';
2123
import { Colors } from '~/styles/colors.style';
@@ -25,9 +27,9 @@ const props = defineProps({
2527
type: Object as PropType<ListScrollItem>,
2628
required: true,
2729
},
28-
index: {
30+
height: {
2931
type: Number,
30-
required: true,
32+
required: false,
3133
},
3234
poster: {
3335
type: String,
@@ -70,30 +72,16 @@ const props = defineProps({
7072
});
7173
7274
const emit = defineEmits<{
73-
(e: 'onHover', event: { index: number; item: ListScrollItem; hover: boolean }): void;
74-
(
75-
e: 'onScrollIntoView',
76-
event: { item: ListScrollItem; index: number; ref?: HTMLDivElement },
77-
): void;
78-
(
79-
e: 'onScrollOutOfView',
80-
event: { item: ListScrollItem; index: number; ref?: HTMLDivElement },
81-
): void;
75+
(e: 'onHover', event: { item: ListScrollItem; hover: boolean }): void;
76+
(e: 'onScrollIntoView', event: { item: ListScrollItem; ref?: HTMLDivElement }): void;
77+
(e: 'onScrollOutOfView', event: { item: ListScrollItem; ref?: HTMLDivElement }): void;
8278
}>();
8379
84-
const {
85-
item,
86-
index,
87-
noHeader,
88-
nextHasHeader,
89-
poster,
90-
episode,
91-
hideDate,
92-
scrollIntoView,
93-
} = toRefs(props);
80+
const { item, noHeader, nextHasHeader, poster, episode, hideDate, scrollIntoView } =
81+
toRefs(props);
9482
9583
const onHover = (_hover: boolean) => {
96-
emit('onHover', { index: index?.value, item: item?.value, hover: _hover });
84+
emit('onHover', { item: item?.value, hover: _hover });
9785
};
9886
9987
const noHead = computed(
@@ -142,7 +130,6 @@ onMounted(() => {
142130
if (!scrollIntoView.value) return;
143131
emit('onScrollIntoView', {
144132
item: item?.value,
145-
index: index.value,
146133
ref: itemRef.value?.$el,
147134
});
148135
});
@@ -151,10 +138,11 @@ onBeforeUnmount(() => {
151138
if (!scrollIntoView.value) return;
152139
emit('onScrollOutOfView', {
153140
item: item?.value,
154-
index: index.value,
155141
ref: itemRef.value?.$el,
156142
});
157143
});
144+
145+
const ListScrollItemTypeLocal = ListScrollItemType;
158146
</script>
159147

160148
<template>
@@ -167,8 +155,10 @@ onBeforeUnmount(() => {
167155
'next-has-header': nextHasHead,
168156
'show-date': !hideDate,
169157
}"
158+
:style="{
159+
'--list-item-height': height ? `${height}px` : undefined,
160+
}"
170161
:data-key="item.id"
171-
:data-index="index"
172162
:line-type="loading ? 'dashed' : lineType"
173163
:color="loading ? 'grey' : color"
174164
@mouseenter="onHover(true)"
@@ -210,7 +200,11 @@ onBeforeUnmount(() => {
210200
<NSkeleton class="loading week" text round />
211201
</template>
212202
</NFlex>
213-
<NFlex class="tile" :wrap="false">
203+
204+
<slot v-if="item.type === ListScrollItemTypeLocal.placeholder">
205+
// TODO default placeholder
206+
</slot>
207+
<NFlex v-else class="tile" :wrap="false">
214208
<NImage
215209
alt="poster-image"
216210
class="poster"
@@ -234,7 +228,7 @@ onBeforeUnmount(() => {
234228
:fallback-src="PosterPlaceholder"
235229
/>
236230
<ListItemPanel :item="item" :loading="loading" :hide-date="hideDate">
237-
<slot :item="item" :index="index" :loading="loading" />
231+
<slot :item="item" :loading="loading" />
238232
</ListItemPanel>
239233
</NFlex>
240234
</NFlex>
@@ -250,6 +244,7 @@ onBeforeUnmount(() => {
250244
@use '~/styles/z-index' as layers;
251245
252246
.timeline-item {
247+
height: var(--list-item-height, 145px);
253248
margin: 0 1rem;
254249
255250
&.show-date {
@@ -336,7 +331,7 @@ onBeforeUnmount(() => {
336331
337332
&.loading {
338333
opacity: 0;
339-
transition: opacity 0s;
334+
transition: opacity 0.1s;
340335
}
341336
342337
&.episode {

src/components/common/list/ListItemPanel.vue

+19-19
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { NEllipsis, NFlex, NSkeleton, NTag } from 'naive-ui';
33
44
import { computed, defineProps, type PropType, toRefs } from 'vue';
55
6-
import type { ListScrollItem } from '~/components/common/list/ListScroll.model';
7-
86
import PosterPlaceholder from '~/assets/images/poster-placholder.webp';
7+
import { type ListScrollItem } from '~/components/common/list/ListScroll.model';
8+
99
import { useI18n } from '~/utils';
1010
1111
const i18n = useI18n('list-item-panel');
@@ -79,23 +79,23 @@ const tags = computed(
7979
<NEllipsis v-else :line-clamp="2">{{ content }}</NEllipsis>
8080
</div>
8181
<NFlex v-if="date || tags?.length" size="medium" class="tags">
82-
<NTag v-if="date" class="tag meta" size="small">
83-
<NSkeleton v-if="loading" text style="width: 20%" round />
84-
<span>{{ date }} </span>
85-
</NTag>
86-
<NTag
87-
v-for="tag of tags"
88-
:key="tag.label"
89-
class="tag"
90-
:class="{
91-
meta: tag.meta,
92-
}"
93-
size="small"
94-
:type="tag.type"
95-
:bordered="tag.bordered ?? true"
96-
>
97-
{{ tag.label }}
98-
</NTag>
82+
<template v-if="date">
83+
<NSkeleton v-if="loading" text style="width: 6%" />
84+
<NTag v-else class="tag meta" size="small"> {{ date }} </NTag>
85+
</template>
86+
<template v-for="tag of tags" :key="tag.label">
87+
<NSkeleton v-if="loading" text style="width: 6%" />
88+
<NTag
89+
v-else
90+
class="tag"
91+
:class="{ meta: tag.meta }"
92+
size="small"
93+
:type="tag.type"
94+
:bordered="tag.bordered ?? true"
95+
>
96+
{{ tag.label }}
97+
</NTag>
98+
</template>
9999
</NFlex>
100100
</NFlex>
101101
</template>

src/components/common/list/ListScroll.model.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type OnUpdated = (listRef: Ref<VirtualListRef | undefined>) => void;
2323

2424
export type ListScrollSourceItem = {
2525
id: string | number;
26+
type?: keyof typeof ListScrollItemType;
2627

2728
movie?: TraktMovie<'short'>;
2829
show?: TraktShow<'short'>;
@@ -45,14 +46,16 @@ export const ListScrollItemType = {
4546
season: 'season',
4647
episode: 'episode',
4748
person: 'person',
49+
loading: 'loading',
50+
loadMore: 'load-more',
4851
placeholder: 'placeholder',
4952
} as const;
5053

5154
export type ListScrollItem = {
52-
id: string | number | 'load-more' | 'empty';
55+
id: string | number;
5356
index: number;
5457

55-
type?: keyof typeof ListScrollItemType;
58+
type?: (typeof ListScrollItemType)[keyof typeof ListScrollItemType];
5659
title?: string;
5760
content?: string;
5861
tags?: ListScrollItemTag[];

src/components/common/list/ListScroll.vue

+33-33
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@ import { ref, toRefs } from 'vue';
55
66
import type { PropType, Ref, Transition } from 'vue';
77
8-
import type {
9-
ListScrollItem,
10-
VirtualListProps,
11-
VirtualListRef,
12-
} from '~/components/common/list/ListScroll.model';
138
import type { TraktClientPagination } from '~/models/trakt/trakt-client.model';
149
1510
import ListEmpty from '~/components/common/list/ListEmpty.vue';
1611
import ListItem from '~/components/common/list/ListItem.vue';
1712
import ListLoadMore from '~/components/common/list/ListLoadMore.vue';
13+
import {
14+
type ListScrollItem,
15+
ListScrollItemType,
16+
type VirtualListProps,
17+
type VirtualListRef,
18+
} from '~/components/common/list/ListScroll.model';
1819
1920
const listRef = ref<VirtualListRef>();
2021
@@ -72,14 +73,8 @@ const emits = defineEmits<{
7273
pageSize: number;
7374
},
7475
): void;
75-
(
76-
e: 'onScrollIntoView',
77-
event: { item: ListScrollItem; index: number; ref?: HTMLDivElement },
78-
): void;
79-
(
80-
e: 'onScrollOutOfView',
81-
event: { item: ListScrollItem; index: number; ref?: HTMLDivElement },
82-
): void;
76+
(e: 'onScrollIntoView', event: { item: ListScrollItem; ref?: HTMLDivElement }): void;
77+
(e: 'onScrollOutOfView', event: { item: ListScrollItem; ref?: HTMLDivElement }): void;
8378
}>();
8479
8580
defineExpose({
@@ -104,7 +99,9 @@ const onScrollHandler = async (e: Event) => {
10499
scrolled.value = true;
105100
}
106101
if (scrollHeight !== scrollTop + clientHeight) return;
107-
if (pagination?.value?.page === pagination?.value?.pageCount) return;
102+
if (pagination?.value && pagination?.value?.page === pagination?.value?.pageCount) {
103+
return;
104+
}
108105
return emits('onScrollBottom', listRef);
109106
};
110107
@@ -120,6 +117,8 @@ const onHover = ({ item, hover }: { item: ListScrollItem; hover: boolean }) => {
120117
const onLoadMore = (payload: { page: number; pageCount: number; pageSize: number }) => {
121118
emits('onloadMore', { listRef, ...payload });
122119
};
120+
121+
const ListScrollItemTypeLocal = ListScrollItemType;
123122
</script>
124123

125124
<template>
@@ -146,28 +145,29 @@ const onLoadMore = (payload: { page: number; pageCount: number; pageSize: number
146145
@vue:updated="onUpdatedHandler"
147146
>
148147
<template #default="{ item }">
149-
<NFlex
150-
v-if="item.id === 'load-more'"
151-
class="load-more"
152-
justify="center"
153-
align="center"
154-
vertical
155-
size="small"
156-
:theme-overrides="{ gapSmall: '0' }"
157-
:style="`height: ${listOptions?.itemSize ?? 145}px;`"
158-
>
159-
<ListLoadMore
160-
:page="pagination?.page"
161-
:page-count="pagination?.pageCount"
162-
:page-size="pageSize"
163-
@on-load-more="onLoadMore"
164-
/>
165-
</NFlex>
148+
<slot v-if="item.type === ListScrollItemTypeLocal.loadMore" name="load-more">
149+
<NFlex
150+
class="load-more"
151+
justify="center"
152+
align="center"
153+
vertical
154+
size="small"
155+
:theme-overrides="{ gapSmall: '0' }"
156+
:style="`height: ${listOptions?.itemSize ?? 145}px;`"
157+
>
158+
<ListLoadMore
159+
:page="pagination?.page"
160+
:page-count="pagination?.pageCount"
161+
:page-size="pageSize"
162+
@on-load-more="onLoadMore"
163+
/>
164+
</NFlex>
165+
</slot>
166166
<ListItem
167167
v-else
168168
:key="item.id"
169169
:item="item"
170-
:index="item.index"
170+
:height="listOptions?.itemSize ?? 145"
171171
:size="items.length"
172172
:hide-date="hideDate"
173173
:episode="episode"
@@ -177,7 +177,7 @@ const onLoadMore = (payload: { page: number; pageCount: number; pageSize: number
177177
@on-scroll-into-view="(...args) => $emit('onScrollIntoView', ...args)"
178178
@on-scroll-out-of-view="(...args) => $emit('onScrollOutOfView', ...args)"
179179
>
180-
<slot :item="item" :index="item.index" :loading="item.loading" />
180+
<slot :item="item" :loading="item.loading" />
181181
</ListItem>
182182
</template>
183183
</NVirtualList>

src/components/common/list/use-list-scroll.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const useListScroll = <D extends string, T extends ListScrollSourceItemWi
9292
const array = items.value;
9393
if (!array.length) return [];
9494
return array.map((item, index) => {
95-
const _item: ListScrollItem = { ...item, index, loading: typeof item.id === 'number' && item.id < 0 };
95+
const _item: ListScrollItem = { ...item, index, loading: (typeof item.id === 'number' && item.id < 0) || item.type === 'loading' };
9696

9797
if (!_item.type) _item.type = getType(item);
9898
if (!_item.title) _item.title = getTitle(item);
@@ -153,8 +153,8 @@ export const addLoadMore = (
153153
if (!pagination.value?.page) return array;
154154
if (!pagination.value?.pageCount) return array;
155155
if (pagination.value.page === pagination.value.pageCount) return array;
156-
if (array.length && array[array.length - 1].id === 'load-more') return array;
157-
const loadMore: ListScrollItem = { id: 'load-more', index: items.value.length };
156+
if (array.length && array[array.length - 1].type === ListScrollItemType.loadMore) return array;
157+
const loadMore: ListScrollItem = { id: 'load-more', type: ListScrollItemType.loadMore, index: items.value.length };
158158
return [...array, loadMore];
159159
});
160160
};

0 commit comments

Comments
 (0)