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:
@@ -8,6 +8,8 @@
|
||||
required?: boolean;
|
||||
icon?: LucideIcon;
|
||||
gradient?: boolean;
|
||||
size?: 'default' | 'hero';
|
||||
onselect?: (file: File) => void;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
@@ -17,9 +19,16 @@
|
||||
required = false,
|
||||
icon: Icon = ImageIcon,
|
||||
gradient = false,
|
||||
class: klass = 'h-40 w-40 sm:h-52 sm:w-52 rounded-control'
|
||||
size = 'default',
|
||||
onselect,
|
||||
class: klass = ''
|
||||
}: Props = $props();
|
||||
|
||||
const SIZE: Record<'default' | 'hero', string> = {
|
||||
default: 'h-40 w-40 sm:h-52 sm:w-52 rounded-control',
|
||||
hero: 'size-cover-hero rounded-art-lg'
|
||||
};
|
||||
|
||||
let preview = $state('');
|
||||
let fallbackFailed = $state(false);
|
||||
|
||||
@@ -27,14 +36,16 @@
|
||||
|
||||
function onInput(event: Event) {
|
||||
const file = (event.currentTarget as HTMLInputElement).files?.[0];
|
||||
if (file) preview = URL.createObjectURL(file);
|
||||
if (!file) return;
|
||||
preview = URL.createObjectURL(file);
|
||||
onselect?.(file);
|
||||
}
|
||||
</script>
|
||||
|
||||
<label
|
||||
class="relative grid cursor-pointer place-items-center overflow-hidden transition-colors {gradient
|
||||
? ''
|
||||
: 'border border-line bg-surface hover:border-accent/50'} {klass}"
|
||||
: 'border border-line bg-surface hover:border-accent/50'} {SIZE[size]} {klass}"
|
||||
style={gradient && !hasImage ? 'background:var(--mf-cover-grad)' : undefined}
|
||||
>
|
||||
<input
|
||||
@@ -57,6 +68,6 @@
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
{:else}
|
||||
<Icon class="h-9 w-9 {gradient ? 'text-on-art/70' : 'text-muted'}" strokeWidth={1.3} />
|
||||
<Icon class="size-icon-xl {gradient ? 'text-on-art/70' : 'text-muted'}" strokeWidth={1.3} />
|
||||
{/if}
|
||||
</label>
|
||||
|
||||
Reference in New Issue
Block a user