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).
77 lines
2.1 KiB
Svelte
77 lines
2.1 KiB
Svelte
<script lang="ts">
|
|
import Plus from '@lucide/svelte/icons/plus';
|
|
import Disc from '@lucide/svelte/icons/disc-3';
|
|
import Page from '$lib/components/ui/Page.svelte';
|
|
import PageHeader from '$lib/components/ui/PageHeader.svelte';
|
|
import MediaCard from '$lib/components/ui/MediaCard.svelte';
|
|
import AlbumForm from '$lib/components/ui/AlbumForm.svelte';
|
|
import Modal from '$lib/components/ui/Modal.svelte';
|
|
import EmptyState from '$lib/components/ui/EmptyState.svelte';
|
|
import Button from '$lib/components/ui/Button.svelte';
|
|
import { albumMeta } from '$lib/format';
|
|
|
|
let { data, form } = $props();
|
|
|
|
const items = $derived(data.albums.items);
|
|
|
|
let creating = $state(false);
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>Álbumes</title>
|
|
<meta name="description" content="Los álbumes que has publicado con tu música subida." />
|
|
</svelte:head>
|
|
|
|
<Page>
|
|
<PageHeader title="Álbumes" description="Agrupa la música que subes en discos.">
|
|
{#snippet actions()}
|
|
{#if items.length > 0}
|
|
<Button onclick={() => (creating = true)}>
|
|
<Plus class="h-4 w-4" strokeWidth={2.5} />
|
|
Nuevo álbum
|
|
</Button>
|
|
{/if}
|
|
{/snippet}
|
|
</PageHeader>
|
|
|
|
{#if items.length > 0}
|
|
<div class="grid-cards">
|
|
{#each items as album, i (album.id)}
|
|
<MediaCard
|
|
href="/albums/{album.id}"
|
|
title={album.title}
|
|
subtitle={albumMeta(
|
|
album.releaseYear === null ? undefined : Number(album.releaseYear),
|
|
Number(album.trackCount)
|
|
)}
|
|
trackIds={album.coverTrackIds}
|
|
src="/api/albums/{album.id}/cover?size=large"
|
|
index={i}
|
|
/>
|
|
{/each}
|
|
</div>
|
|
{:else}
|
|
<EmptyState
|
|
icon={Disc}
|
|
title="Todavía no tienes álbumes"
|
|
description="Crea el primero y añádele las canciones que hayas subido."
|
|
>
|
|
{#snippet actions()}
|
|
<Button onclick={() => (creating = true)}>Crear álbum</Button>
|
|
{/snippet}
|
|
</EmptyState>
|
|
{/if}
|
|
</Page>
|
|
|
|
<Modal open={creating} onClose={() => (creating = false)} title="Nuevo álbum">
|
|
<AlbumForm
|
|
action="?/create"
|
|
titlePlaceholder="Mi primer disco"
|
|
requireCover
|
|
formMessage={form?.message}
|
|
submitLabel="Crear"
|
|
submittingLabel="Creando…"
|
|
onCancel={() => (creating = false)}
|
|
/>
|
|
</Modal>
|