Skip to content

Commit c3cbbc5

Browse files
committed
feat(date): adds date to list scroll
1 parent e6e1208 commit c3cbbc5

File tree

6 files changed

+157
-55
lines changed

6 files changed

+157
-55
lines changed

src/components/common/list/ListItem.vue

+121-28
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
2-
import { NFlex, NImage, NTimelineItem } from 'naive-ui';
2+
import { NFlex, NImage, NSkeleton, NTime, NTimelineItem } from 'naive-ui';
33
4-
import { type PropType } from 'vue';
4+
import { computed, type PropType } from 'vue';
55
66
import type { ListScrollItem } from '~/components/common/list/ListScroll.model';
77
@@ -33,23 +33,53 @@ const props = defineProps({
3333
required: false,
3434
default: 'default',
3535
},
36-
noTag: {
36+
noHeader: {
37+
type: Boolean,
38+
required: false,
39+
},
40+
nextHasHeader: {
41+
type: Boolean,
42+
required: false,
43+
},
44+
hideDate: {
45+
type: Boolean,
46+
required: false,
47+
},
48+
hover: {
3749
type: Boolean,
3850
required: false,
3951
},
4052
});
53+
54+
const emit = defineEmits<{
55+
(e: 'onHover', event: { index: number; item: ListScrollItem; hover: boolean }): void;
56+
}>();
57+
58+
const onHover = (hover: boolean) => {
59+
emit('onHover', { index: props.index, item: props.item, hover });
60+
};
61+
62+
const noHeader = computed(() => props.noHeader || props.item.date?.sameDayAsPrevious);
63+
const nextHasHead = computed(
64+
() => props.nextHasHeader || !props.item.date?.sameDayAsNext,
65+
);
66+
const date = computed(() => props.item.date?.current);
67+
const year = new Date().getFullYear();
68+
const sameYear = computed(() => date.value?.getFullYear() === year);
69+
const loading = computed(() => props.item.loading);
4170
</script>
4271

4372
<template>
4473
<NTimelineItem
4574
:key="item.id"
4675
class="timeline-item"
47-
:data-tag="JSON.stringify(item.date)"
48-
:class="{ 'no-tag': noTag || item.date?.sameDay }"
76+
:class="{ 'no-header': noHeader, 'next-has-header': nextHasHead }"
4977
:data-key="item.id"
5078
:data-index="index"
5179
:line-type="item.loading ? 'dashed' : lineType"
5280
:color="item.loading ? 'grey' : color"
81+
@mouseenter="onHover(true)"
82+
@mouseleave="onHover(false)"
5383
>
5484
<template #icon>
5585
<slot name="tag" />
@@ -58,19 +88,43 @@ const props = defineProps({
5888
<slot name="before" />
5989
</template>
6090
<template #default>
61-
<NFlex class="content" :class="{ 'no-border': noTag || item.date?.sameDay }">
62-
<NImage
63-
alt="poster-image"
64-
class="poster"
65-
lazy
66-
preview-disabled
67-
:src="poster"
68-
:preview-src="poster"
69-
:fallback-src="PosterPlaceholder"
70-
/>
71-
<ListItemPanel :item="item" :loading="item.loading">
72-
<slot :item="item" :index="index" :loading="item.loading" />
73-
</ListItemPanel>
91+
<NFlex class="content" :class="{ 'no-border': noHeader }" :wrap="false">
92+
<NFlex
93+
v-if="!hideDate"
94+
class="header"
95+
:class="{ hover }"
96+
vertical
97+
justify="flex-start"
98+
align="center"
99+
size="small"
100+
:theme-overrides="{ gapSmall: '0.25rem' }"
101+
>
102+
<template v-if="date && !noHeader">
103+
<NTime class="month" :time="date" format="MMM" />
104+
<NTime class="day" :time="date" format="dd" />
105+
<NTime :time="date" format="eee" />
106+
<NTime v-if="!sameYear" class="year" :time="date" format="yyyy" />
107+
</template>
108+
<template v-else-if="loading">
109+
<NSkeleton text style="width: 1rem" />
110+
<NSkeleton text style="width: 2rem" />
111+
<NSkeleton text style="width: 1rem" />
112+
</template>
113+
</NFlex>
114+
<NFlex class="tile" :wrap="false">
115+
<NImage
116+
alt="poster-image"
117+
class="poster"
118+
lazy
119+
preview-disabled
120+
:src="poster"
121+
:preview-src="poster"
122+
:fallback-src="PosterPlaceholder"
123+
/>
124+
<ListItemPanel :item="item" :loading="item.loading">
125+
<slot :item="item" :index="index" :loading="item.loading" />
126+
</ListItemPanel>
127+
</NFlex>
74128
</NFlex>
75129
</template>
76130
<template #footer>
@@ -81,21 +135,48 @@ const props = defineProps({
81135

82136
<style lang="scss" scoped>
83137
@use '~/styles/mixin' as mixin;
138+
@use '~/styles/z-index' as layers;
84139
85140
.timeline-item {
86141
margin: 0 1rem;
87142
88143
.content {
89-
@include mixin.hover-background(
90-
$from: transparent,
91-
$to: var(--bg-color-20),
92-
$transition: 0.2s var(--n-bezier)
93-
);
94-
95144
padding: 0.5rem;
96145
97-
&:not(.no-border) {
98-
border-top: 1px solid rgba(255 255 255 / 10%);
146+
.tile {
147+
@include mixin.hover-background(
148+
$from: transparent,
149+
$to: var(--bg-color-20),
150+
$transition: 0.2s var(--n-bezier)
151+
);
152+
153+
flex: 1 1 auto;
154+
}
155+
}
156+
157+
.header {
158+
width: 2.5rem;
159+
margin: 0.25rem 0.5rem 0 -0.25rem;
160+
color: var(--n-text-color);
161+
font-size: 14px;
162+
163+
&.hover {
164+
color: var(--trakt-red);
165+
transition: color 0.2s var(--n-bezier);
166+
will-change: color;
167+
}
168+
169+
.month {
170+
font-weight: bold;
171+
}
172+
173+
.day {
174+
font-weight: bold;
175+
font-size: 1.5rem;
176+
}
177+
178+
.year {
179+
color: var(--n-meta-text-color);
99180
}
100181
}
101182
@@ -117,10 +198,10 @@ const props = defineProps({
117198
118199
.n-timeline.n-timeline--left-placement {
119200
.timeline-item.n-timeline-item .n-timeline-item-timeline {
120-
top: -6px;
201+
top: calc(0% - var(--n-icon-size) / 2);
121202
}
122203
123-
.timeline-item.no-tag.n-timeline-item .n-timeline-item-timeline {
204+
.timeline-item.no-header.n-timeline-item .n-timeline-item-timeline {
124205
.n-timeline-item-timeline__line {
125206
top: 0;
126207
}
@@ -129,5 +210,17 @@ const props = defineProps({
129210
display: none;
130211
}
131212
}
213+
214+
.timeline-item.n-timeline-item .n-timeline-item-content {
215+
border-top: 1px solid transparent;
216+
217+
.n-timeline-item-content__title {
218+
margin: 0;
219+
}
220+
}
221+
222+
.timeline-item.n-timeline-item:not(.no-header) .n-timeline-item-content {
223+
border-top: 1px solid rgba(255 255 255 / 10%);
224+
}
132225
}
133226
</style>

src/components/common/list/ListItemPanel.vue

+13-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { NFlex, NSkeleton } from 'naive-ui';
2+
import { NEllipsis, NFlex, NSkeleton } from 'naive-ui';
33
44
import { computed, type PropType, toRefs } from 'vue';
55
@@ -40,10 +40,8 @@ const content = computed(() => {
4040
return media.show?.title;
4141
});
4242
43-
const date = computed(() => {
44-
const media = item.value;
45-
return media.date?.current?.toLocaleString();
46-
});
43+
const currentDate = computed(() => item.value.date?.current);
44+
const date = computed(() => currentDate.value?.toLocaleTimeString());
4745
4846
const type = computed(() => {
4947
const media = item.value;
@@ -57,25 +55,25 @@ const type = computed(() => {
5755
<NFlex
5856
class="panel"
5957
vertical
60-
size="small"
6158
justify="center"
59+
size="small"
6260
:theme-overrides="{ gapSmall: '0' }"
6361
>
62+
<div class="meta type">
63+
<NSkeleton v-if="loading" text style="width: 10%" />
64+
<NEllipsis v-else :line-clamp="1">{{ type }}</NEllipsis>
65+
</div>
6466
<div class="title">
6567
<NSkeleton v-if="loading" text style="width: 70%" />
66-
<template v-else>{{ title }}</template>
68+
<NEllipsis v-else :line-clamp="2">{{ title }}</NEllipsis>
6769
</div>
6870
<div class="content">
6971
<NSkeleton v-if="loading" text style="width: 60%" />
70-
<template v-else>{{ content }}</template>
71-
</div>
72-
<div class="meta type">
73-
<NSkeleton v-if="loading" text style="width: 10%" />
74-
<template v-else>{{ type }}</template>
72+
<NEllipsis v-else :line-clamp="2">{{ content }}</NEllipsis>
7573
</div>
7674
<div class="meta time">
7775
<NSkeleton v-if="loading" text style="width: 20%" />
78-
<template v-else>{{ date }}</template>
76+
<NEllipsis v-else :line-clamp="1">{{ date }}</NEllipsis>
7977
</div>
8078
</NFlex>
8179
</template>
@@ -87,8 +85,7 @@ const type = computed(() => {
8785
8886
.title {
8987
font-variant-numeric: tabular-nums;
90-
margin: var(--n-title-margin);
91-
color: var(--n-title-text-color);
88+
color: var(--trakt-red);
9289
font-weight: var(--n-title-font-weight);
9390
font-size: var(--n-title-font-size);
9491
transition: color 0.3s var(--n-bezier);
@@ -107,11 +104,7 @@ const type = computed(() => {
107104
}
108105
109106
.time {
110-
margin-top: 0.5rem;
111-
}
112-
113-
.type {
114-
margin-top: 0.5rem;
107+
margin-top: 0.25rem;
115108
}
116109
}
117110
</style>

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export type ListScrollItem = {
2626
previous?: Date;
2727
current: Date;
2828
next?: Date;
29-
sameDay?: boolean;
29+
sameDayAsPrevious?: boolean;
30+
sameDayAsNext?: boolean;
3031
};
3132
};

src/components/common/list/ListScroll.vue

+17-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import type { TraktClientPagination } from '~/models/trakt/trakt-client.model';
1414
1515
import ListEmpty from '~/components/common/list/ListEmpty.vue';
1616
import ListItem from '~/components/common/list/ListItem.vue';
17-
import { debounce } from '~/utils/debounce.utils';
1817
1918
const virtualList = ref<VirtualListRef>();
2019
@@ -39,6 +38,10 @@ const props = defineProps({
3938
type: Object as PropType<VirtualListProps>,
4039
required: false,
4140
},
41+
hideDate: {
42+
type: Boolean,
43+
required: false,
44+
},
4245
});
4346
4447
const emits = defineEmits<{
@@ -64,7 +67,10 @@ const onUpdatedHandler = () => {
6467
return emits('onUpdated', virtualList);
6568
};
6669
67-
const debounceLog = debounce(e => console.info('top', e), 100);
70+
const hoverDate = ref<string>();
71+
const onHover = ({ item, hover }: { item: ListScrollItem; hover: boolean }) => {
72+
if (hover) hoverDate.value = item.date?.current?.toDateString();
73+
};
6874
</script>
6975

7076
<template>
@@ -87,9 +93,15 @@ const debounceLog = debounce(e => console.info('top', e), 100);
8793
@scroll="onScrollHandler"
8894
@vue:updated="onUpdatedHandler"
8995
>
90-
<template #default="{ item, index }">
91-
<ListItem :item="item" :index="index">
92-
<slot :item="item" :index="index" :loading="item.loading" />
96+
<template #default="{ item }">
97+
<ListItem
98+
:item="item"
99+
:index="item.index"
100+
:hide-date="hideDate"
101+
:hover="hoverDate === item.date?.current?.toDateString()"
102+
@on-hover="onHover"
103+
>
104+
<slot :item="item" :index="item.index" :loading="item.loading" />
93105
</ListItem>
94106
</template>
95107
</NVirtualList>

src/components/views/history/HistoryComponent.vue

+3-1
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ const history = computed<ListScrollItem[]>(() => {
5353
if (array[index + 1]?.watched_at) {
5454
date.next = new Date(array[index + 1]?.watched_at);
5555
}
56-
date.sameDay =
56+
date.sameDayAsPrevious =
5757
date.previous?.toLocaleDateString() === date.current?.toLocaleDateString();
58+
date.sameDayAsNext =
59+
date.next?.toLocaleDateString() === date.current?.toLocaleDateString();
5860
return { ..._item, date };
5961
});
6062
});

src/components/views/watchlist/WatchlistComponent.vue

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const list = computed<ListScrollItem[]>(() => {
4646
:loading="loading"
4747
:pagination="pagination"
4848
:page-size="pageSize"
49+
hide-date
4950
>
5051
<template #default>
5152
<!-- TODO buttons here-->

0 commit comments

Comments
 (0)