Split the explore page into genre grid, top result and search results components
This commit is contained in:
@@ -1,23 +1,12 @@
|
||||
<script lang="ts">
|
||||
import Music from '@lucide/svelte/icons/music';
|
||||
import { player } from '$lib/player/player.svelte';
|
||||
import type { Paged, Track } from '$lib/types';
|
||||
import { fmtTime, fmtPlays, plural } from '$lib/utils/format';
|
||||
import { plural } from '$lib/utils/format';
|
||||
import Page from '$lib/components/ui/layout/Page.svelte';
|
||||
import PageHeader from '$lib/components/ui/layout/PageHeader.svelte';
|
||||
import Alert from '$lib/components/ui/primitives/Alert.svelte';
|
||||
import EmptyState from '$lib/components/ui/primitives/EmptyState.svelte';
|
||||
import InfiniteScroll from '$lib/components/ui/primitives/InfiniteScroll.svelte';
|
||||
import Artwork from '$lib/components/ui/media/Artwork.svelte';
|
||||
import Avatar from '$lib/components/ui/media/Avatar.svelte';
|
||||
import UserRow from '$lib/components/ui/media/UserRow.svelte';
|
||||
import PlayButton from '$lib/components/ui/media/PlayButton.svelte';
|
||||
import EqBars from '$lib/components/ui/media/EqBars.svelte';
|
||||
import ListRow from '$lib/components/ui/media/ListRow.svelte';
|
||||
import Chip from '$lib/components/ui/primitives/Chip.svelte';
|
||||
import SectionHeading from '$lib/components/ui/layout/SectionHeading.svelte';
|
||||
import { EXPLORE_PAGE_SIZE } from '$lib/config';
|
||||
import { genreHref, playlistCover } from '$lib/utils/hrefs';
|
||||
import { genreHref } from '$lib/utils/hrefs';
|
||||
import { createMenu } from '$lib/state/menu.svelte';
|
||||
import { createPagedList } from '$lib/state/pagedList.svelte';
|
||||
import TrackContextMenu from '$lib/components/music/TrackContextMenu.svelte';
|
||||
@@ -25,21 +14,14 @@
|
||||
import { genreInfo, genreTiles } from '$lib/data/genres';
|
||||
import {
|
||||
type SearchFilter,
|
||||
showGroup,
|
||||
capped,
|
||||
matchPlaylists,
|
||||
SEARCH_LABELS,
|
||||
searchTotal,
|
||||
searchChips,
|
||||
findTopResult
|
||||
} from '$lib/data/search';
|
||||
|
||||
const TOP_RESULT_KIND_LABEL: Record<TopResult['kind'], string> = {
|
||||
track: 'Canción',
|
||||
album: 'Álbum',
|
||||
playlist: 'Playlist',
|
||||
user: 'Usuario'
|
||||
};
|
||||
import GenreGrid from './GenreGrid.svelte';
|
||||
import TopResult from './TopResult.svelte';
|
||||
import SearchResults from './SearchResults.svelte';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
@@ -96,18 +78,7 @@
|
||||
const chips = $derived(searchChips(counts));
|
||||
const totalHits = $derived(searchTotal(counts));
|
||||
|
||||
type TopResult = NonNullable<
|
||||
ReturnType<
|
||||
typeof findTopResult<
|
||||
Track,
|
||||
(typeof albums)[number],
|
||||
(typeof playlistMatches)[number],
|
||||
(typeof users)[number]
|
||||
>
|
||||
>
|
||||
>;
|
||||
|
||||
const topResult = $derived<TopResult | null>(
|
||||
const topResult = $derived(
|
||||
sfilter === 'all' && data.query
|
||||
? findTopResult({
|
||||
query: data.query,
|
||||
@@ -118,18 +89,6 @@
|
||||
})
|
||||
: null
|
||||
);
|
||||
|
||||
function playTopResult() {
|
||||
if (topResult?.kind !== 'track') return;
|
||||
player.playOrToggle([topResult.track], 0);
|
||||
}
|
||||
|
||||
const topResultHref = $derived.by(() => {
|
||||
if (!topResult || topResult.kind === 'track') return undefined;
|
||||
if (topResult.kind === 'album') return `/albums/${topResult.album.id}`;
|
||||
if (topResult.kind === 'playlist') return `/playlists/${topResult.playlist.id}`;
|
||||
return `/user/${topResult.user.id}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -174,62 +133,7 @@
|
||||
</div>
|
||||
|
||||
{#if topResult}
|
||||
{#snippet topResultBody()}
|
||||
{#if topResult.kind === 'track'}
|
||||
<Artwork
|
||||
trackIds={[topResult.track.id]}
|
||||
size="xl"
|
||||
alt={topResult.track.title}
|
||||
class="shrink-0"
|
||||
/>
|
||||
{:else if topResult.kind === 'album'}
|
||||
<Artwork trackIds={topResult.album.coverTrackIds} size="xl" class="shrink-0" />
|
||||
{:else if topResult.kind === 'playlist'}
|
||||
<Artwork
|
||||
src={playlistCover(topResult.playlist, 'large')}
|
||||
trackIds={topResult.playlist.coverTrackIds}
|
||||
size="xl"
|
||||
class="shrink-0"
|
||||
/>
|
||||
{:else}
|
||||
<Avatar name={topResult.user.name} src={topResult.user.profilePictureUrl} size="lg" />
|
||||
{/if}
|
||||
|
||||
<div class="flex h-22 min-w-0 flex-1 flex-col justify-center gap-3">
|
||||
<div class="text-eyebrow leading-none font-semibold text-fg-2">
|
||||
Mejor resultado · {TOP_RESULT_KIND_LABEL[topResult.kind]}
|
||||
</div>
|
||||
<div
|
||||
class="truncate font-display text-lg leading-none font-medium tracking-display text-fg"
|
||||
>
|
||||
{topResult.kind === 'track'
|
||||
? topResult.track.title
|
||||
: topResult.kind === 'album'
|
||||
? topResult.album.title
|
||||
: topResult.kind === 'playlist'
|
||||
? topResult.playlist.name
|
||||
: topResult.user.name}
|
||||
</div>
|
||||
{#if topResult.kind === 'track' && topResult.track.artist}
|
||||
<div class="truncate text-sm leading-none text-fg-2">
|
||||
{topResult.track.artist}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<PlayButton as="span" size="lg" label="Reproducir" class="group-hover/top:brightness-110" />
|
||||
{/snippet}
|
||||
|
||||
<svelte:element
|
||||
this={topResult.kind === 'track' ? 'button' : 'a'}
|
||||
type={topResult.kind === 'track' ? 'button' : undefined}
|
||||
role={topResult.kind === 'track' ? 'button' : 'link'}
|
||||
href={topResult.kind === 'track' ? undefined : topResultHref}
|
||||
onclick={topResult.kind === 'track' ? playTopResult : undefined}
|
||||
class="group/top animate-pop mt-6.5 flex w-full items-center gap-5 rounded-panel-lg bg-surface p-4.5 text-left transition hover:bg-surface-hover"
|
||||
>
|
||||
{@render topResultBody()}
|
||||
</svelte:element>
|
||||
<TopResult result={topResult} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -255,137 +159,22 @@
|
||||
{/if}
|
||||
|
||||
{#if !data.query && !data.genre}
|
||||
<div class="grid-wide">
|
||||
{#each tiles as tile, i (tile.genre)}
|
||||
<a
|
||||
href={genreHref(tile.genre)}
|
||||
class="animate-enter group relative flex h-28 flex-col gap-1 overflow-hidden rounded-panel p-4 transition-[filter] genre-tile hover:brightness-110"
|
||||
style="--i:{i}; --tile-hue:{tile.hue}"
|
||||
>
|
||||
<div class="text-on-art">
|
||||
{tile.label}
|
||||
</div>
|
||||
<div class="text-xs text-on-art-2">{tile.tagline}</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{#if tiles.length === 0}
|
||||
<EmptyState icon={Music} description="Todavía no hay géneros. Sube música para empezar." />
|
||||
{/if}
|
||||
<GenreGrid {tiles} />
|
||||
{/if}
|
||||
|
||||
{#if (data.query || data.genre) && !nothingFound}
|
||||
<div class="mt-9 flex flex-col gap-9">
|
||||
{#if showGroup(sfilter, 'tracks')}
|
||||
<div>
|
||||
{#if items.length > 0}
|
||||
<SectionHeading title={SEARCH_LABELS.tracks} count={counts.tracks} />
|
||||
<ul class="flex flex-col gap-1">
|
||||
{#each capped(sfilter, items) as track, i (track.id)}
|
||||
<li>
|
||||
<ListRow
|
||||
title={track.title}
|
||||
subtitle={track.artist}
|
||||
subtitleHref={track.ownerUserId}
|
||||
explicit={track.explicit}
|
||||
active={player.currentId === track.id}
|
||||
size="lg"
|
||||
trackId={track.id}
|
||||
onclick={() => player.playOrToggle(items, i)}
|
||||
oncontextmenu={(e) => trackMenu.open(e, track)}
|
||||
>
|
||||
{#snippet overlay()}
|
||||
{#if player.currentId === track.id}
|
||||
<EqBars overlay paused={!player.playing} />
|
||||
{/if}
|
||||
{/snippet}
|
||||
{#snippet trailing()}
|
||||
<span class="hidden shrink-0 text-xs text-fg-2 tabular-nums sm:block">
|
||||
{fmtTime(track.duration)} · {fmtPlays(track.listensCount)}
|
||||
</span>
|
||||
{/snippet}
|
||||
</ListRow>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if sfilter === 'tracks' && tracksList.hasMore}
|
||||
<InfiniteScroll
|
||||
onLoadMore={() => tracksList.loadMore()}
|
||||
hasMore={tracksList.hasMore}
|
||||
loading={tracksList.loading}
|
||||
error={tracksList.error}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<EmptyState
|
||||
icon={Music}
|
||||
description={activeGenre
|
||||
? 'Todavía no hay canciones de este género.'
|
||||
: `No hay canciones que coincidan con «${data.query}».`}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if hasAlbums && showGroup(sfilter, 'albums')}
|
||||
<div>
|
||||
<SectionHeading title={SEARCH_LABELS.albums} count={counts.albums} />
|
||||
<ul class="flex flex-col gap-1">
|
||||
{#each capped(sfilter, albums) as album (album.id)}
|
||||
<li>
|
||||
<ListRow
|
||||
title={album.title}
|
||||
href="/albums/{album.id}"
|
||||
size="lg"
|
||||
trackIds={album.coverTrackIds}
|
||||
oncontextmenu={(e) => albumMenu.open(e, album.id)}
|
||||
>
|
||||
{#snippet trailing()}
|
||||
<span class="hidden shrink-0 text-xs text-fg-2 tabular-nums sm:block">
|
||||
{plural(album.trackCount, 'canción', 'canciones')}
|
||||
</span>
|
||||
{/snippet}
|
||||
</ListRow>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if hasUsers && showGroup(sfilter, 'users')}
|
||||
<div>
|
||||
<SectionHeading title={SEARCH_LABELS.users} count={counts.users} />
|
||||
<ul class="flex flex-col gap-1">
|
||||
{#each capped(sfilter, users) as u (u.id)}
|
||||
<li>
|
||||
<UserRow user={u} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if hasPlaylists && showGroup(sfilter, 'playlists')}
|
||||
<div>
|
||||
<SectionHeading title={SEARCH_LABELS.playlists} count={counts.playlists} />
|
||||
<ul class="flex flex-col gap-1">
|
||||
{#each capped(sfilter, playlistMatches) as playlist (playlist.id)}
|
||||
<li>
|
||||
<ListRow
|
||||
title={playlist.name}
|
||||
subtitle={playlist.description}
|
||||
href="/playlists/{playlist.id}"
|
||||
size="lg"
|
||||
coverSrc={playlistCover(playlist, 'medium')}
|
||||
trackIds={playlist.coverTrackIds}
|
||||
/>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<SearchResults
|
||||
filter={sfilter}
|
||||
{counts}
|
||||
query={data.query}
|
||||
inGenre={!!activeGenre}
|
||||
{tracksList}
|
||||
{albums}
|
||||
playlists={playlistMatches}
|
||||
{users}
|
||||
{trackMenu}
|
||||
{albumMenu}
|
||||
/>
|
||||
{/if}
|
||||
</Page>
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import Music from '@lucide/svelte/icons/music';
|
||||
import EmptyState from '$lib/components/ui/primitives/EmptyState.svelte';
|
||||
import { genreHref } from '$lib/utils/hrefs';
|
||||
import type { genreTiles } from '$lib/data/genres';
|
||||
|
||||
interface Props {
|
||||
tiles: ReturnType<typeof genreTiles>;
|
||||
}
|
||||
|
||||
let { tiles }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="grid-wide">
|
||||
{#each tiles as tile, i (tile.genre)}
|
||||
<a
|
||||
href={genreHref(tile.genre)}
|
||||
class="animate-enter group relative flex h-28 flex-col gap-1 overflow-hidden rounded-panel p-4 transition-[filter] genre-tile hover:brightness-110"
|
||||
style="--i:{i}; --tile-hue:{tile.hue}"
|
||||
>
|
||||
<div class="text-on-art">
|
||||
{tile.label}
|
||||
</div>
|
||||
<div class="text-xs text-on-art-2">{tile.tagline}</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{#if tiles.length === 0}
|
||||
<EmptyState icon={Music} description="Todavía no hay géneros. Sube música para empezar." />
|
||||
{/if}
|
||||
@@ -0,0 +1,168 @@
|
||||
<script lang="ts">
|
||||
import Music from '@lucide/svelte/icons/music';
|
||||
import { player } from '$lib/player/player.svelte';
|
||||
import type { Album, Playlist, Track } from '$lib/types';
|
||||
import { fmtTime, fmtPlays, plural } from '$lib/utils/format';
|
||||
import EmptyState from '$lib/components/ui/primitives/EmptyState.svelte';
|
||||
import InfiniteScroll from '$lib/components/ui/primitives/InfiniteScroll.svelte';
|
||||
import UserRow from '$lib/components/ui/media/UserRow.svelte';
|
||||
import EqBars from '$lib/components/ui/media/EqBars.svelte';
|
||||
import ListRow from '$lib/components/ui/media/ListRow.svelte';
|
||||
import SectionHeading from '$lib/components/ui/layout/SectionHeading.svelte';
|
||||
import { playlistCover } from '$lib/utils/hrefs';
|
||||
import type { Menu } from '$lib/state/menu.svelte';
|
||||
import type { PagedList } from '$lib/state/pagedList.svelte';
|
||||
import {
|
||||
type SearchCounts,
|
||||
type SearchFilter,
|
||||
showGroup,
|
||||
capped,
|
||||
SEARCH_LABELS
|
||||
} from '$lib/data/search';
|
||||
|
||||
interface ResultUser {
|
||||
id: string;
|
||||
name: string;
|
||||
profilePictureUrl: string | null;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
filter: SearchFilter;
|
||||
counts: SearchCounts;
|
||||
query: string;
|
||||
inGenre: boolean;
|
||||
tracksList: PagedList<Track>;
|
||||
albums: Album[];
|
||||
playlists: Playlist[];
|
||||
users: ResultUser[];
|
||||
trackMenu: Menu<Track>;
|
||||
albumMenu: Menu<string>;
|
||||
}
|
||||
|
||||
let {
|
||||
filter,
|
||||
counts,
|
||||
query,
|
||||
inGenre,
|
||||
tracksList,
|
||||
albums,
|
||||
playlists,
|
||||
users,
|
||||
trackMenu,
|
||||
albumMenu
|
||||
}: Props = $props();
|
||||
|
||||
const items = $derived(tracksList.items);
|
||||
</script>
|
||||
|
||||
<div class="mt-9 flex flex-col gap-9">
|
||||
{#if showGroup(filter, 'tracks')}
|
||||
<div>
|
||||
{#if items.length > 0}
|
||||
<SectionHeading title={SEARCH_LABELS.tracks} count={counts.tracks} />
|
||||
<ul class="flex flex-col gap-1">
|
||||
{#each capped(filter, items) as track, i (track.id)}
|
||||
<li>
|
||||
<ListRow
|
||||
title={track.title}
|
||||
subtitle={track.artist}
|
||||
subtitleHref={track.ownerUserId}
|
||||
explicit={track.explicit}
|
||||
active={player.currentId === track.id}
|
||||
size="lg"
|
||||
trackId={track.id}
|
||||
onclick={() => player.playOrToggle(items, i)}
|
||||
oncontextmenu={(e) => trackMenu.open(e, track)}
|
||||
>
|
||||
{#snippet overlay()}
|
||||
{#if player.currentId === track.id}
|
||||
<EqBars overlay paused={!player.playing} />
|
||||
{/if}
|
||||
{/snippet}
|
||||
{#snippet trailing()}
|
||||
<span class="hidden shrink-0 text-xs text-fg-2 tabular-nums sm:block">
|
||||
{fmtTime(track.duration)} · {fmtPlays(track.listensCount)}
|
||||
</span>
|
||||
{/snippet}
|
||||
</ListRow>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if filter === 'tracks' && tracksList.hasMore}
|
||||
<InfiniteScroll
|
||||
onLoadMore={() => tracksList.loadMore()}
|
||||
hasMore={tracksList.hasMore}
|
||||
loading={tracksList.loading}
|
||||
error={tracksList.error}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<EmptyState
|
||||
icon={Music}
|
||||
description={inGenre
|
||||
? 'Todavía no hay canciones de este género.'
|
||||
: `No hay canciones que coincidan con «${query}».`}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if albums.length > 0 && showGroup(filter, 'albums')}
|
||||
<div>
|
||||
<SectionHeading title={SEARCH_LABELS.albums} count={counts.albums} />
|
||||
<ul class="flex flex-col gap-1">
|
||||
{#each capped(filter, albums) as album (album.id)}
|
||||
<li>
|
||||
<ListRow
|
||||
title={album.title}
|
||||
href="/albums/{album.id}"
|
||||
size="lg"
|
||||
trackIds={album.coverTrackIds}
|
||||
oncontextmenu={(e) => albumMenu.open(e, album.id)}
|
||||
>
|
||||
{#snippet trailing()}
|
||||
<span class="hidden shrink-0 text-xs text-fg-2 tabular-nums sm:block">
|
||||
{plural(album.trackCount, 'canción', 'canciones')}
|
||||
</span>
|
||||
{/snippet}
|
||||
</ListRow>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if users.length > 0 && showGroup(filter, 'users')}
|
||||
<div>
|
||||
<SectionHeading title={SEARCH_LABELS.users} count={counts.users} />
|
||||
<ul class="flex flex-col gap-1">
|
||||
{#each capped(filter, users) as u (u.id)}
|
||||
<li>
|
||||
<UserRow user={u} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if playlists.length > 0 && showGroup(filter, 'playlists')}
|
||||
<div>
|
||||
<SectionHeading title={SEARCH_LABELS.playlists} count={counts.playlists} />
|
||||
<ul class="flex flex-col gap-1">
|
||||
{#each capped(filter, playlists) as playlist (playlist.id)}
|
||||
<li>
|
||||
<ListRow
|
||||
title={playlist.name}
|
||||
subtitle={playlist.description}
|
||||
href="/playlists/{playlist.id}"
|
||||
size="lg"
|
||||
coverSrc={playlistCover(playlist, 'medium')}
|
||||
trackIds={playlist.coverTrackIds}
|
||||
/>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,90 @@
|
||||
<script lang="ts">
|
||||
import { player } from '$lib/player/player.svelte';
|
||||
import Artwork from '$lib/components/ui/media/Artwork.svelte';
|
||||
import Avatar from '$lib/components/ui/media/Avatar.svelte';
|
||||
import PlayButton from '$lib/components/ui/media/PlayButton.svelte';
|
||||
import { playlistCover } from '$lib/utils/hrefs';
|
||||
import type { Album, Playlist, Track } from '$lib/types';
|
||||
import type { TopResult as TopResultData } from '$lib/data/search';
|
||||
|
||||
interface ResultUser {
|
||||
id: string;
|
||||
name: string;
|
||||
profilePictureUrl: string | null;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
result: TopResultData<Track, Album, Playlist, ResultUser>;
|
||||
}
|
||||
|
||||
let { result }: Props = $props();
|
||||
|
||||
const KIND_LABEL: Record<Props['result']['kind'], string> = {
|
||||
track: 'Canción',
|
||||
album: 'Álbum',
|
||||
playlist: 'Playlist',
|
||||
user: 'Usuario'
|
||||
};
|
||||
|
||||
function play() {
|
||||
if (result.kind !== 'track') return;
|
||||
player.playOrToggle([result.track], 0);
|
||||
}
|
||||
|
||||
const href = $derived.by(() => {
|
||||
if (result.kind === 'track') return undefined;
|
||||
if (result.kind === 'album') return `/albums/${result.album.id}`;
|
||||
if (result.kind === 'playlist') return `/playlists/${result.playlist.id}`;
|
||||
return `/user/${result.user.id}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#snippet body()}
|
||||
{#if result.kind === 'track'}
|
||||
<Artwork trackIds={[result.track.id]} size="xl" alt={result.track.title} class="shrink-0" />
|
||||
{:else if result.kind === 'album'}
|
||||
<Artwork trackIds={result.album.coverTrackIds} size="xl" class="shrink-0" />
|
||||
{:else if result.kind === 'playlist'}
|
||||
<Artwork
|
||||
src={playlistCover(result.playlist, 'large')}
|
||||
trackIds={result.playlist.coverTrackIds}
|
||||
size="xl"
|
||||
class="shrink-0"
|
||||
/>
|
||||
{:else}
|
||||
<Avatar name={result.user.name} src={result.user.profilePictureUrl} size="lg" />
|
||||
{/if}
|
||||
|
||||
<div class="flex h-22 min-w-0 flex-1 flex-col justify-center gap-3">
|
||||
<div class="text-eyebrow leading-none font-semibold text-fg-2">
|
||||
Mejor resultado · {KIND_LABEL[result.kind]}
|
||||
</div>
|
||||
<div class="truncate font-display text-lg leading-none font-medium tracking-display text-fg">
|
||||
{result.kind === 'track'
|
||||
? result.track.title
|
||||
: result.kind === 'album'
|
||||
? result.album.title
|
||||
: result.kind === 'playlist'
|
||||
? result.playlist.name
|
||||
: result.user.name}
|
||||
</div>
|
||||
{#if result.kind === 'track' && result.track.artist}
|
||||
<div class="truncate text-sm leading-none text-fg-2">
|
||||
{result.track.artist}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<PlayButton as="span" size="lg" label="Reproducir" class="group-hover/top:brightness-110" />
|
||||
{/snippet}
|
||||
|
||||
<svelte:element
|
||||
this={result.kind === 'track' ? 'button' : 'a'}
|
||||
type={result.kind === 'track' ? 'button' : undefined}
|
||||
role={result.kind === 'track' ? 'button' : 'link'}
|
||||
{href}
|
||||
onclick={result.kind === 'track' ? play : undefined}
|
||||
class="group/top animate-pop mt-6.5 flex w-full items-center gap-5 rounded-panel-lg bg-surface p-4.5 text-left transition hover:bg-surface-hover"
|
||||
>
|
||||
{@render body()}
|
||||
</svelte:element>
|
||||
Reference in New Issue
Block a user