Files
Musify/web-player/src/lib/components/ui/PlaylistForm.svelte
T
2026-09-20 16:00:57 +02:00

162 lines
4.3 KiB
Svelte

<script lang="ts">
import { untrack } from 'svelte';
import { enhance } from '$app/forms';
import Music from '@lucide/svelte/icons/music';
import ImageDropzone from './ImageDropzone.svelte';
import Button from './Button.svelte';
import Field from './Field.svelte';
import Input from './Input.svelte';
import Textarea from './Textarea.svelte';
import Alert from './Alert.svelte';
const NAME_MAX_LENGTH = 60;
const DESCRIPTION_MAX_LENGTH = 300;
interface Props {
action: string;
initialName?: string;
initialDescription?: string;
initialVisibility?: 'private' | 'public';
namePlaceholder?: string;
coverFallbackUrl?: string;
formMessage?: string;
submitLabel: string;
submittingLabel: string;
helperText?: string;
onCancel: () => void;
onSuccess?: () => void;
}
let {
action,
initialName = '',
initialDescription = '',
initialVisibility = 'private',
namePlaceholder = 'Playlist sin título',
coverFallbackUrl,
formMessage,
submitLabel,
submittingLabel,
helperText,
onCancel,
onSuccess
}: Props = $props();
let name = $state(untrack(() => initialName));
let description = $state(untrack(() => initialDescription));
let submitting = $state(false);
let visibility = $state(untrack(() => initialVisibility));
</script>
<form
method="POST"
{action}
enctype="multipart/form-data"
use:enhance={() => {
submitting = true;
return async ({ update, result }) => {
await update({ reset: false });
submitting = false;
if (result.type === 'success' || result.type === 'redirect') onSuccess?.();
};
}}
class="mt-5 w-full max-w-4xl space-y-5"
>
<input type="hidden" name="visibility" value={visibility} />
<div class="flex flex-col items-stretch gap-5 sm:flex-row sm:gap-8">
<Field>
<ImageDropzone
name="cover"
fallbackUrl={coverFallbackUrl}
icon={Music}
gradient
class="h-36 w-36 rounded-2xl"
/>
</Field>
<div class="flex flex-1 flex-col gap-2">
<Field label="Nombre" for="playlist-name">
{#snippet hint()}
<span class="ml-auto tabular-nums">{name.length}/{NAME_MAX_LENGTH}</span>
{/snippet}
<Input
id="playlist-name"
name="name"
maxlength={NAME_MAX_LENGTH}
required
bind:value={name}
placeholder={namePlaceholder}
/>
</Field>
<Field label="Descripción" for="playlist-description" class="min-h-0 flex-1">
<Textarea
id="playlist-description"
name="description"
maxlength={DESCRIPTION_MAX_LENGTH}
bind:value={description}
placeholder="Para qué sirve esta playlist, cuándo la escuchas…"
class="flex-1"
/>
</Field>
</div>
</div>
<div class="flex items-center justify-between gap-4 rounded-art bg-surface px-4 py-3.5">
<div class="min-w-0">
<div class="text-sm font-medium text-fg-2">Visibilidad</div>
<div class="mt-0.5 text-xs text-muted">
{visibility === 'private'
? 'Solo tú la ves en tu biblioteca'
: 'Cualquiera con el enlace puede verla'}
</div>
</div>
<div class="relative flex shrink-0 rounded-full bg-surface-2 p-1">
<div
class="absolute inset-y-1 left-1 w-[calc(50%-4px)] rounded-full bg-fg transition-transform duration-300 ease-out {visibility ===
'public'
? 'translate-x-full'
: ''}"
></div>
<button
type="button"
onclick={() => (visibility = 'private')}
class="relative z-10 w-20 rounded-full py-1.5 text-center text-xs font-medium transition-colors {visibility ===
'private'
? 'text-ink'
: 'text-muted hover:text-fg-2'}"
>
Privada
</button>
<button
type="button"
onclick={() => (visibility = 'public')}
class="relative z-10 w-20 rounded-full py-1.5 text-center text-xs font-medium transition-colors {visibility ===
'public'
? 'text-ink'
: 'text-muted hover:text-fg-2'}"
>
Pública
</button>
</div>
</div>
{#if formMessage}
<Alert tone="danger">{formMessage}</Alert>
{/if}
<div class="flex items-center justify-between gap-4">
{#if helperText}
<p class="text-sm text-muted">{helperText}</p>
{:else}
<span></span>
{/if}
<div class="flex shrink-0 items-center gap-3">
<Button type="button" variant="secondary" onclick={onCancel}>Cancelar</Button>
<Button type="submit" disabled={submitting || name.trim() === ''}>
{submitting ? submittingLabel : submitLabel}
</Button>
</div>
</div>
</form>