PageHeader fusiona CollectionHeader (title/eyebrow/description/meta/
cover?/actions?/align?): sin cover se comporta como el antiguo
PageHeader simple (acciones en línea), con cover como el antiguo
CollectionHeader (acciones debajo). Fija su propio margen inferior
(mb-7/8); se quitan los mt-* que ponía cada página después. Migra los
9 sitios que usaban uno u otro (álbumes, biblioteca, listas, subir,
Explorar ×2, me gusta, mezcla, perfil). CollectionHeader se borra.
AlbumHeader y PlaylistHeader se inlinean en sus páginas y se borran.
Decisión (recomendada, sin bloquear): PlaylistHeader gana iconos en
Editar/Eliminar/Reproducir, igual que ya tenía AlbumHeader.
ConfirmDialog nuevo sustituye los dos diálogos de borrado (álbum y
playlist), que tenían tipografías distintas. Modal pasa a usar
.glass-panel (en vez de duplicar el style= inline) y z-(--z-modal); su
eyebrow y título usan las recetas text-eyebrow/text-display-3.
SectionHeading gana `count?` (línea "N resultados", sustituye 4 copias
en Explorar) y `href?`/`linkLabel?` ("Ver todas", sustituye la de
Inicio). Aplica recetas de tipografía (text-display-2/3,
text-eyebrow, text-body) y tokens de ancho (max-w-prose-sm, max-w-hero)
en EmptyState, +error, "Sin resultados", hero y stats de Inicio, Queue
y Sidebar. El estado vacío "Todavía no has escuchado nada" (repetido
2 veces en Inicio) pasa a un snippet local.
Verificado con npm run check/build; sin QA visual autenticada (Docker
no disponible en esta sesión).
75 lines
2.2 KiB
Svelte
75 lines
2.2 KiB
Svelte
<script lang="ts">
|
|
import Heart from '@lucide/svelte/icons/heart';
|
|
import X from '@lucide/svelte/icons/x';
|
|
import { player, isQueueCurrent, playAllOrToggle } from '$lib/player/player.svelte';
|
|
import { liked } from '$lib/player/liked.svelte';
|
|
import { plural } from '$lib/format';
|
|
import Page from '$lib/components/ui/Page.svelte';
|
|
import PageHeader from '$lib/components/ui/PageHeader.svelte';
|
|
import Button from '$lib/components/ui/Button.svelte';
|
|
import EmptyState from '$lib/components/ui/EmptyState.svelte';
|
|
import TrackList from '$lib/components/ui/TrackList.svelte';
|
|
|
|
const tracks = $derived(liked.list);
|
|
const listTracks = $derived(tracks.map((track) => ({ ...track, addedAt: track.likedAt })));
|
|
const isCurrentQueue = $derived(isQueueCurrent(tracks));
|
|
|
|
function playFrom(index: number) {
|
|
player.playOrToggle(tracks, index);
|
|
}
|
|
|
|
function playAll() {
|
|
playAllOrToggle(tracks);
|
|
}
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>Me gusta</title>
|
|
<meta name="description" content="Las canciones que te gustan en Musify." />
|
|
</svelte:head>
|
|
|
|
<Page>
|
|
<PageHeader
|
|
eyebrow="Colección"
|
|
title="Me gusta"
|
|
meta={plural(tracks.length, 'canción', 'canciones')}
|
|
align="center"
|
|
>
|
|
{#snippet cover()}
|
|
<div
|
|
class="grid size-cover-hero shrink-0 place-items-center rounded-art-lg bg-[image:var(--mf-liked-grad)] sm:size-cover-hero-sm"
|
|
>
|
|
<Heart class="size-icon-xl text-on-art/90" fill="currentColor" strokeWidth={0} />
|
|
</div>
|
|
{/snippet}
|
|
{#snippet actions()}
|
|
<Button size="sm" onclick={playAll} disabled={tracks.length === 0}>
|
|
{isCurrentQueue && player.playing ? 'Pausar' : 'Reproducir'}
|
|
</Button>
|
|
{/snippet}
|
|
</PageHeader>
|
|
|
|
{#if tracks.length > 0}
|
|
<TrackList
|
|
tracks={listTracks}
|
|
columns={['added', 'plays']}
|
|
onPlay={playFrom}
|
|
rowAction={{
|
|
onclick: (track) => liked.toggle({ ...track, artist: track.artist ?? undefined }),
|
|
icon: X,
|
|
label: 'Quitar de Me gusta'
|
|
}}
|
|
/>
|
|
{:else}
|
|
<EmptyState
|
|
icon={Heart}
|
|
title="Todavía no tienes canciones con me gusta"
|
|
description="Toca el corazón en cualquier canción del reproductor para guardarla aquí."
|
|
>
|
|
{#snippet actions()}
|
|
<Button href="/explore" variant="secondary">Explorar música</Button>
|
|
{/snippet}
|
|
</EmptyState>
|
|
{/if}
|
|
</Page>
|