Files
Musify/web-player/src/lib/components/ui/AlbumForm.svelte
T
Admin 1a2bafe96c 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).
2026-09-21 00:58:52 +02:00

95 lines
2.1 KiB
Svelte

<script lang="ts">
import { untrack } from 'svelte';
import Disc3 from '@lucide/svelte/icons/disc-3';
import CoverForm from './CoverForm.svelte';
import Field from './Field.svelte';
import Input from './Input.svelte';
import Textarea from './Textarea.svelte';
import { ALBUM_EARLIEST_YEAR } from '$lib/config';
interface Props {
action: string;
initialTitle?: string;
initialDescription?: string;
initialReleaseYear?: number;
titlePlaceholder?: string;
coverFallbackUrl?: string;
requireCover?: boolean;
formMessage?: string;
submitLabel: string;
submittingLabel: string;
onCancel: () => void;
onSuccess?: () => void;
}
let {
action,
initialTitle = '',
initialDescription = '',
initialReleaseYear,
titlePlaceholder = '',
coverFallbackUrl,
requireCover = false,
formMessage,
submitLabel,
submittingLabel,
onCancel,
onSuccess
}: Props = $props();
let title = $state(untrack(() => initialTitle));
let description = $state(untrack(() => initialDescription));
let releaseYear = $state(untrack(() => (initialReleaseYear ? String(initialReleaseYear) : '')));
const latestYear = new Date().getFullYear() + 1;
</script>
<CoverForm
{action}
{coverFallbackUrl}
{requireCover}
coverIcon={Disc3}
{formMessage}
{submitLabel}
{submittingLabel}
canSubmit={title.trim() !== ''}
{onCancel}
{onSuccess}
>
{#snippet fields()}
<Field label="Título" for="album-title">
<Input
id="album-title"
name="title"
maxlength={200}
required
bind:value={title}
placeholder={titlePlaceholder}
/>
</Field>
<Field label="Año de publicación" for="album-year">
<Input
id="album-year"
name="releaseYear"
type="number"
min={ALBUM_EARLIEST_YEAR}
max={latestYear}
bind:value={releaseYear}
placeholder={String(new Date().getFullYear())}
/>
</Field>
{/snippet}
{#snippet extra()}
<Field label="Descripción" for="album-description">
<Textarea
id="album-description"
name="description"
maxlength={256}
bind:value={description}
placeholder="¿De qué va este álbum?"
/>
</Field>
{/snippet}
</CoverForm>