Merge branch 'feat/artist-page'

This commit is contained in:
lucast committed 2026-04-05 12:02:53 +02:00
commit c868329d8b
6 files changed
+428 -7

No files matched your search

+25 -6
View File
@@ -1,22 +1,39 @@
<script setup lang="ts">
interface SliderProps {
title?: string
scrollWidth?: () => number
sliderClass?: string
}
const props = defineProps<SliderProps>()
const slider = ref<HTMLDivElement>()
const slider = useTemplateRef<HTMLDivElement>("slider")
const scrollLeft = (num: number) => {
if (slider.value) slider.value.scrollLeft += num
if (!slider.value) return
const maxScroll = slider.value.scrollWidth - slider.value.clientWidth
slider.value.scrollLeft = Math.max(
0,
Math.min(slider.value?.scrollLeft + num, maxScroll),
)
}
const left = () => {
if (slider.value) scrollLeft(-slider.value.offsetWidth)
if (slider.value)
scrollLeft(
props.scrollWidth
? -props.scrollWidth()
: -slider.value.offsetWidth,
)
}
const right = () => {
if (slider.value) scrollLeft(slider.value.offsetWidth)
if (slider.value)
scrollLeft(
props.scrollWidth ? props.scrollWidth() : slider.value.offsetWidth,
)
}
const doesScroll = ref<boolean>(false)
@@ -48,7 +65,9 @@ onMounted(() => {
class="left"
name="material-symbols:chevron-left-rounded"
/>
<section class="slider" ref="slider"><slot /></section>
<section class="slider" :class="[props.sliderClass]" ref="slider">
<slot />
</section>
<Icon
v-if="doesScroll"
@click="right"
@@ -85,7 +104,7 @@ onMounted(() => {
& > * {
flex: 0 0 auto;
scroll-snap-align: center;
scroll-snap-align: start;
}
}
+141
View File
@@ -0,0 +1,141 @@
<script setup lang="ts">
import type { DropdownMenuItem } from "~/ui/DropdownMenu.vue"
interface TrackRowProps {
track: Track
}
const { track } = defineProps<TrackRowProps>()
const emit = defineEmits<{ (e: "playAlbumAtIndex"): void }>()
const hovering = ref(false)
const toast = useToast()
const dropdownMenuItems: DropdownMenuItem[][] = [
[
{
label: "Play only this",
icon: "lucide:play",
onSelect() {},
},
{
label: "Play next",
icon: "lucide:list-start",
onSelect() {
toast.show("Playing next")
},
},
{
label: "Add to queue",
icon: "lucide:list-end",
onSelect() {
toast.show("Added to queue")
},
},
],
]
</script>
<template>
<article
class="track"
@mouseover="hovering = true"
@mouseleave="hovering = false"
>
<div
class="index"
:class="{ hovering }"
@click="emit('playAlbumAtIndex')"
>
<Icon name="lucide:play" />
<NuxtImg
:src="GET_FILE(track.album.file)"
width="50"
height="50"
placeholder
/>
</div>
<div>
<p>{{ track.name }}</p>
<ArtistName :artists="track.artists" class="artists" />
</div>
<DropdownMenu :items="dropdownMenuItems">
<Icon name="lucide:ellipsis" class="dropdown-icon" />
</DropdownMenu>
</article>
</template>
<style lang="scss" scoped>
@use "@/styles/variables" as *;
@use "@/styles/util" as *;
@use "sass:color";
.track {
display: grid;
grid-template-columns: 40px 1fr 30px;
gap: 0.5rem;
align-items: center;
padding: 0.6rem 1rem;
border-radius: $border-radius-standard;
background-color: $color-background;
line-height: 1;
width: 100%;
p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.index {
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
position: relative;
cursor: pointer;
img {
border-radius: $border-radius-standard;
}
span {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
opacity: 0;
}
img,
span {
transition: opacity 0.15s ease;
}
&.hovering {
img {
opacity: 0.4;
}
span {
opacity: 1;
}
}
}
.artists {
@include fontSize(14px);
}
.dropdown-icon {
font-size: 20px;
cursor: pointer;
}
</style>