web-player: consistent imports, tokens, glossary and app route grouping

This commit is contained in:
2026-09-24 11:52:57 +02:00
parent 1a2ae37f49
commit 975fa7e4fd
32 changed files with 71 additions and 69 deletions
@@ -3,10 +3,10 @@
import Upload from '@lucide/svelte/icons/upload';
import Folder from '@lucide/svelte/icons/folder';
import LogIn from '@lucide/svelte/icons/log-in';
import MenuItem from '../ui/overlay/MenuItem.svelte';
import AccountMenu from '../ui/overlay/AccountMenu.svelte';
import IconButton from '../ui/primitives/IconButton.svelte';
import Logo from '../ui/primitives/Logo.svelte';
import MenuItem from '$lib/components/ui/overlay/MenuItem.svelte';
import AccountMenu from '$lib/components/ui/overlay/AccountMenu.svelte';
import IconButton from '$lib/components/ui/primitives/IconButton.svelte';
import Logo from '$lib/components/ui/primitives/Logo.svelte';
interface Props {
user: SessionUser | null;
@@ -5,10 +5,10 @@
import { liked } from '$lib/player/liked.svelte';
import { createPlaylistModal } from '$lib/state/panels.svelte';
import type { SessionUser } from '$lib/types';
import ListRow from '../ui/media/ListRow.svelte';
import IconButton from '../ui/primitives/IconButton.svelte';
import ListRow from '$lib/components/ui/media/ListRow.svelte';
import IconButton from '$lib/components/ui/primitives/IconButton.svelte';
import Plus from '@lucide/svelte/icons/plus';
import Logo from '../ui/primitives/Logo.svelte';
import Logo from '$lib/components/ui/primitives/Logo.svelte';
const SIDEBAR_PLAYLISTS_LIMIT = 8;
@@ -33,8 +33,7 @@
</script>
<aside
class="hidden min-h-0 shrink-0 flex-col border-r border-hairline p-5 lg:flex"
style="width:var(--mf-sidebar-w)"
class="hidden min-h-0 w-(--mf-sidebar-w) shrink-0 flex-col border-r border-hairline p-5 lg:flex"
>
<a href="/" class="mb-4">
<Logo size="sm" />
@@ -75,7 +74,7 @@
{/if}
</div>
<div class="flex min-h-0 flex-1 flex-col gap-px overflow-y-auto pb-icon-md">
<div class="flex min-h-0 flex-1 flex-col gap-px overflow-y-auto pb-4.5">
{#each visiblePlaylists as playlist (playlist.id)}
{@const active = page.url.pathname === `/playlists/${playlist.id}`}
<ListRow
@@ -93,8 +92,8 @@
onclick={() => createPlaylistModal.show()}
class="flex items-center gap-2 rounded-control px-3 py-2 text-xs text-fg-2 transition-colors hover:bg-hover"
>
<Plus class="size-3.5" />
Crear tu primera lista
<Plus class="size-icon-xs" />
Crear tu primera playlist
</button>
{/each}
</div>
@@ -25,7 +25,7 @@
? 'text-fg'
: 'text-fg-2'}"
>
<link.icon class="size-4" />
<link.icon class="size-icon-sm" />
<span class="text-xs font-medium">{link.label}</span>
</a>
{/each}
@@ -7,9 +7,9 @@
import Search from '@lucide/svelte/icons/search';
import X from '@lucide/svelte/icons/x';
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
import AccountMenu from '../ui/overlay/AccountMenu.svelte';
import IconButton from '../ui/primitives/IconButton.svelte';
import Button from '../ui/primitives/Button.svelte';
import AccountMenu from '$lib/components/ui/overlay/AccountMenu.svelte';
import IconButton from '$lib/components/ui/primitives/IconButton.svelte';
import Button from '$lib/components/ui/primitives/Button.svelte';
interface Props {
user: SessionUser | null;
@@ -61,7 +61,7 @@
<label
class="flex h-9.5 w-full items-center gap-2.5 rounded-control bg-surface-2 px-3.5 transition focus-within:bg-surface-hover"
>
<Search class="size-4 shrink-0 text-fg-2" />
<Search class="size-icon-sm shrink-0 text-fg-2" />
<input
type="text"
bind:this={searchInput}
@@ -77,7 +77,7 @@
aria-label="Borrar búsqueda"
class="grid size-5 shrink-0 place-items-center rounded-full text-fg-2 transition hover:text-fg"
>
<X class="size-3.5" />
<X class="size-icon-xs" />
</button>
{/if}
</label>
@@ -17,7 +17,7 @@
</script>
<div
class="animate-enter pointer-events-auto flex items-center gap-icon-md px-4 py-3 glass-bar sm:hidden"
class="animate-enter pointer-events-auto flex items-center gap-4.5 px-4 py-3 glass-bar sm:hidden"
>
<div class="min-w-0 flex-1">
<TrackInfo compact />
@@ -1,6 +1,6 @@
<script lang="ts">
import PlayerBar from './PlayerBar.svelte';
import TabsBar from '../layout/TabsBar.svelte';
import TabsBar from '$lib/components/layout/TabsBar.svelte';
import { player } from '$lib/player/player.svelte';
import type { SessionUser } from '$lib/types';
@@ -43,8 +43,7 @@
inert={!queuePanel.open}
>
<aside
class="flex min-h-0 shrink-0 flex-col border-l border-hairline px-4 pt-5 pb-5"
style="width:var(--mf-queue-w)"
class="flex min-h-0 w-(--mf-queue-w) shrink-0 flex-col border-l border-hairline px-4 pt-5 pb-5"
>
<div class="mb-5 flex items-center justify-between">
<h2 class="font-display text-base font-medium tracking-tight text-fg">En cola</h2>
@@ -26,7 +26,7 @@
aria-label="Aleatorio"
class="transition-colors hover:text-fg {player.shuffle ? 'text-accent' : 'text-fg-2'}"
>
<Shuffle class="size-4" />
<Shuffle class="size-icon-sm" />
</button>
{/if}
@@ -58,7 +58,7 @@
aria-label="Repetir"
class="transition-colors hover:text-fg {player.repeat ? 'text-accent' : 'text-fg-2'}"
>
<Repeat class="size-4" />
<Repeat class="size-icon-sm" />
</button>
{/if}
</div>
@@ -2,9 +2,9 @@
import { untrack } from 'svelte';
import Disc3 from '@lucide/svelte/icons/disc-3';
import CoverForm from './CoverForm.svelte';
import Field from '../primitives/Field.svelte';
import Input from '../primitives/Input.svelte';
import Textarea from '../primitives/Textarea.svelte';
import Field from '$lib/components/ui/primitives/Field.svelte';
import Input from '$lib/components/ui/primitives/Input.svelte';
import Textarea from '$lib/components/ui/primitives/Textarea.svelte';
import { ALBUM_EARLIEST_YEAR, LIMITS } from '$lib/validation';
interface Props {
@@ -3,9 +3,9 @@
import { enhance } from '$app/forms';
import type { LucideIcon } from '@lucide/svelte';
import ImageDropzone from './ImageDropzone.svelte';
import Button from '../primitives/Button.svelte';
import Field from '../primitives/Field.svelte';
import Alert from '../primitives/Alert.svelte';
import Button from '$lib/components/ui/primitives/Button.svelte';
import Field from '$lib/components/ui/primitives/Field.svelte';
import Alert from '$lib/components/ui/primitives/Alert.svelte';
interface Props {
action: string;
@@ -2,10 +2,10 @@
import { untrack } from 'svelte';
import Music from '@lucide/svelte/icons/music';
import CoverForm from './CoverForm.svelte';
import Field from '../primitives/Field.svelte';
import Input from '../primitives/Input.svelte';
import Textarea from '../primitives/Textarea.svelte';
import SegmentedControl from '../primitives/SegmentedControl.svelte';
import Field from '$lib/components/ui/primitives/Field.svelte';
import Input from '$lib/components/ui/primitives/Input.svelte';
import Textarea from '$lib/components/ui/primitives/Textarea.svelte';
import SegmentedControl from '$lib/components/ui/primitives/SegmentedControl.svelte';
import { LIMITS } from '$lib/validation';
@@ -1,7 +1,7 @@
<script lang="ts">
import { untrack } from 'svelte';
import { enhance } from '$app/forms';
import Button from '../primitives/Button.svelte';
import Button from '$lib/components/ui/primitives/Button.svelte';
interface Props {
following: boolean;
@@ -2,7 +2,7 @@
import { player } from '$lib/player/player.svelte';
import { isQueueCurrent, playAllOrToggle } from '$lib/player/actions';
import type { Track } from '$lib/types';
import Button from '../primitives/Button.svelte';
import Button from '$lib/components/ui/primitives/Button.svelte';
interface Props {
items: Track[];
@@ -27,7 +27,7 @@
import { pressable } from '$lib/actions/pressable';
import MediaIdentity from './MediaIdentity.svelte';
import EqBars from './EqBars.svelte';
import IconButton from '../primitives/IconButton.svelte';
import IconButton from '$lib/components/ui/primitives/IconButton.svelte';
interface RowAction {
action?: string;
@@ -86,7 +86,7 @@
style="--mf-track-cols:{gridColumns}; --mf-track-cols-mobile:{gridColumnsMobile}"
>
{#if index}
<Hash size={16} />
<Hash class="size-icon-sm" />
{/if}
<span>Título</span>
{#each columns as column (column)}
@@ -11,7 +11,7 @@
import { themeMode } from '$lib/theme/mode.svelte';
import { clickOutside } from '$lib/actions/clickOutside';
import { onEscape } from '$lib/actions/onEscape';
import Avatar from '../media/Avatar.svelte';
import Avatar from '$lib/components/ui/media/Avatar.svelte';
interface Props {
user: SessionUser;
@@ -1,6 +1,6 @@
<script lang="ts">
import Dialog from './Dialog.svelte';
import Button from '../primitives/Button.svelte';
import Button from '$lib/components/ui/primitives/Button.svelte';
interface Props {
open: boolean;
@@ -4,7 +4,7 @@
import { playlistCover } from '$lib/utils/hrefs';
import type { LucideIcon } from '@lucide/svelte';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
import MediaIdentity from '../media/MediaIdentity.svelte';
import MediaIdentity from '$lib/components/ui/media/MediaIdentity.svelte';
import GlassMenu from './GlassMenu.svelte';
import MenuItem from './MenuItem.svelte';
@@ -1,6 +1,6 @@
<script lang="ts">
import Dialog from './Dialog.svelte';
import Button from '../primitives/Button.svelte';
import Button from '$lib/components/ui/primitives/Button.svelte';
import { dialog, type DialogAction, type DialogOptions } from '$lib/state/dialog.svelte';
// Keep the last dialog's content so the exit transition doesn't render an empty panel.
@@ -1,7 +1,7 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import X from '@lucide/svelte/icons/x';
import IconButton from '../primitives/IconButton.svelte';
import IconButton from '$lib/components/ui/primitives/IconButton.svelte';
import { fade } from 'svelte/transition';
import { pop } from '$lib/utils/transitions';
import { onEscape } from '$lib/actions/onEscape';
@@ -96,7 +96,7 @@
</h2>
</div>
<IconButton label="Cerrar" onclick={onClose} class="shrink-0">
<X class="size-4" />
<X class="size-icon-sm" />
</IconButton>
</div>
{/if}
@@ -28,13 +28,13 @@
children
}: Props = $props();
const sizes = {
const SIZE = {
sm: 'rounded-control px-4 py-2 text-xs',
md: 'rounded-btn-sm px-4.5 py-2.5 text-control-sm',
lg: 'rounded-btn px-6 py-3.5 text-sm'
};
const variants = {
const VARIANT = {
primary: 'bg-cta-strong text-ink hover:bg-cta-strong-hover',
secondary: 'bg-btn text-fg hover:bg-btn-hover',
accent: 'surface-active text-accent-soft hover:surface-active-hover',
@@ -42,7 +42,7 @@
};
const classes = $derived(
`inline-flex items-center justify-center gap-2 font-medium btn-transition focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-bg focus-visible:outline-none disabled:opacity-40 ${sizes[size]} ${variants[variant]} ${klass}`
`inline-flex items-center justify-center gap-2 font-medium btn-transition focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg disabled:opacity-40 ${SIZE[size]} ${VARIANT[variant]} ${klass}`
);
</script>
@@ -57,7 +57,7 @@
class={classes}
>
{#if loading && !href}
<LoaderCircle class="size-4 animate-spin" />
<LoaderCircle class="size-icon-sm animate-spin" />
{/if}
{@render children()}
</svelte:element>
@@ -21,7 +21,7 @@
class="peer size-5 cursor-pointer appearance-none rounded-tag border border-line bg-surface transition checked:border-accent-soft checked:bg-accent-soft focus-visible:border-accent/60 focus-visible:outline-none"
/>
<Check
class="pointer-events-none absolute size-3.5 text-ink opacity-0 transition peer-checked:opacity-100"
class="pointer-events-none absolute size-icon-xs text-ink opacity-0 transition peer-checked:opacity-100"
/>
</span>
<span class="cursor-pointer">{@render children()}</span>
@@ -14,8 +14,8 @@
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-2 rounded-full px-3.5 py-2 text-xs font-medium transition ${
? 'focus-ring rounded-full border border-line-strong px-3.5 py-2 text-xs text-fg-2 transition-colors hover:bg-hover hover:text-fg'
: `focus-ring flex items-center gap-2 rounded-full px-3.5 py-2 text-xs font-medium transition ${
selected ? 'bg-cta-strong text-ink' : 'bg-btn text-fg-2 hover:bg-btn-hover hover:text-fg'
} ${disabled ? 'cursor-not-allowed opacity-40' : ''}`
);
@@ -45,7 +45,7 @@
);
const classes = $derived(
`grid place-items-center transition focus-visible:ring-2 focus-visible:ring-accent focus-visible:outline-none ${SIZE[size]} ${shape === 'round' ? 'rounded-full' : 'rounded-control'} ${pressed ? 'text-accent' : 'text-fg-2 hover:text-fg'} ${background} ${
`grid place-items-center transition focus-ring ${SIZE[size]} ${shape === 'round' ? 'rounded-full' : 'rounded-control'} ${pressed ? 'text-accent' : 'text-fg-2 hover:text-fg'} ${background} ${
revealOnHover ? 'sm:opacity-0 sm:group-hover:opacity-100 sm:focus-visible:opacity-100' : ''
} ${klass}`
);
+2 -2
View File
@@ -5,8 +5,8 @@ const HISTORY_INDEX = 'sveltekit:history';
const LABELS: [RegExp, string][] = [
[/^\/$/, 'Volver al inicio'],
[/^\/explore\/?$/, 'Volver a explorar'],
[/^\/playlists\/[^/]+/, 'Volver a la lista'],
[/^\/playlists\/?$/, 'Volver a tus listas'],
[/^\/playlists\/[^/]+/, 'Volver a la playlist'],
[/^\/playlists\/?$/, 'Volver a tus playlists'],
[/^\/albums\/[^/]+/, 'Volver al álbum'],
[/^\/albums\/?$/, 'Volver a tus álbumes'],
[/^\/library\/?$/, 'Volver a tus canciones'],
@@ -205,7 +205,7 @@
{/if}
<section>
<SectionHeading title="Mixes" subtitle="Generados a partir de lo que más repites" />
<SectionHeading title="Mezclas" subtitle="Generados a partir de lo que más repites" />
{#if data.mixes.length > 0}
<div class="grid-tiles">
{#each data.mixes as mix, i (mix.id)}
@@ -255,11 +255,11 @@
{:else}
<EmptyState
icon={ListMusic}
title="Todavía no tienes listas"
description="Crea tu primera lista y añádele canciones de tu biblioteca."
title="Todavía no tienes playlists"
description="Crea tu primera playlist y añádele canciones de tu biblioteca."
>
{#snippet actions()}
<Button href="/playlists">Crear lista</Button>
<Button href="/playlists">Crear playlist</Button>
{/snippet}
</EmptyState>
{/if}
@@ -28,7 +28,7 @@
{#snippet actions()}
{#if items.length > 0}
<Button onclick={() => (creating = true)}>
<Plus class="size-4" />
<Plus class="size-icon-sm" />
Nuevo álbum
</Button>
{/if}
@@ -36,7 +36,7 @@
{#snippet actions()}
{#if items.length > 0}
<Button href="/upload">
<Upload class="size-4" />
<Upload class="size-icon-sm" />
Subir canción
</Button>
{/if}
@@ -62,7 +62,7 @@
>
{#snippet actions()}
<Button href="/upload">
<Upload class="size-4" />
<Upload class="size-icon-sm" />
Sube la primera
</Button>
{/snippet}
@@ -59,7 +59,7 @@
onclick={() => playShuffled(tracks)}
disabled={tracks.length === 0}
>
<Shuffle class="size-4" />
<Shuffle class="size-icon-sm" />
Aleatorio
</Button>
{/snippet}
@@ -19,18 +19,18 @@
</script>
<svelte:head>
<title>Mis listas</title>
<meta name="description" content="Mis listas en Musify." />
<title>Mis playlists</title>
<meta name="description" content="Mis playlists en Musify." />
</svelte:head>
<Page>
<PageHeader
title="Mis listas"
title="Mis playlists"
description={items.length > 0 ? summary : 'Crea y organiza tus colecciones.'}
>
{#snippet actions()}
{#if items.length > 0}
<Button onclick={() => createPlaylistModal.show()}>Crear nueva lista</Button>
<Button onclick={() => createPlaylistModal.show()}>Crear nueva playlist</Button>
{/if}
{/snippet}
</PageHeader>
@@ -51,11 +51,11 @@
{:else}
<EmptyState
icon={ListMusic}
title="Todavía no tienes listas"
title="Todavía no tienes playlists"
description="Crea la primera y añádele canciones de tu biblioteca."
>
{#snippet actions()}
<Button onclick={() => createPlaylistModal.show()}>Crear nueva lista</Button>
<Button onclick={() => createPlaylistModal.show()}>Crear nueva playlist</Button>
{/snippet}
</EmptyState>
{/if}
@@ -133,7 +133,7 @@
: 'bg-accent-btn text-accent-soft'}"
>
{#if stepStates[i] === 'done'}
<Check class="size-3.5" />
<Check class="size-icon-xs" />
{:else}
{i + 1}
{/if}
+4
View File
@@ -82,6 +82,10 @@
}
}
@utility focus-ring {
@apply focus-visible:ring-2 focus-visible:ring-accent focus-visible:outline-none;
}
@utility track-grid {
grid-template-columns: var(--mf-track-cols-mobile);