web-player: P11 — formularios (CoverForm, SegmentedControl, Chip)

CoverForm.svelte es el esqueleto compartido (form + enhance + portada
+ mensaje de error + botones) de AlbumForm y PlaylistForm, que quedan
solo con sus campos propios vía snippets `fields` (junto a la
portada) y `extra` (a ancho completo, no está en la spec literal pero
necesario: la descripción del álbum y el selector de visibilidad de
playlist no caben en la columna junto al arte).

ImageDropzone gana `size="hero"` (size-cover-hero + rounded-art-lg) y
un callback `onselect`; la página de subir canción usa ahora este
componente en vez de reimplementar el selector de portada a mano
(envuelto en {#key} para poder resetear tras publicar). Su icono de
posición vacía pasa de 64px a icon-xl (36px), como recomienda el plan.

SegmentedControl.svelte (genérico, N opciones) sustituye el selector
Privada/Pública de PlaylistForm. Chip.svelte sustituye los chips de
filtro de Explorar (selected/count) y los chips de género sugeridos en
"Sin resultados" (href, estilo outline).

@utility field-control en layout.css unifica la cadena de clases
repetida de Input y Textarea.

Verificado con npm run check/build; sin QA visual autenticada (Docker
no disponible en esta sesión).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 00:58:52 +02:00
co-authored by Claude Sonnet 5
parent 2cf3e9f02d
commit 90f9a76628
12 changed files with 304 additions and 234 deletions
@@ -0,0 +1,36 @@
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
selected?: boolean;
count?: number;
href?: string;
onclick?: () => void;
children: Snippet;
}
let { selected = false, count, href, onclick, children }: Props = $props();
const classes = $derived(
href
? 'rounded-full border border-line-strong px-3.5 py-2 text-xs text-fg-2 transition-colors hover:bg-hover hover:text-fg'
: `flex items-center gap-1.75 rounded-full px-3.5 py-1.75 text-xs font-medium transition ${
selected
? 'bg-cta-strong text-ink'
: 'bg-surface-2 text-fg-2 hover:bg-surface-hover hover:text-fg'
}`
);
</script>
{#snippet content()}
{@render children()}
{#if count !== undefined}
<span class="tabular-nums {selected ? 'opacity-55' : 'text-muted'}">{count}</span>
{/if}
{/snippet}
{#if href}
<a {href} class={classes}>{@render content()}</a>
{:else}
<button type="button" {onclick} class={classes}>{@render content()}</button>
{/if}