web-player: unify primitive link/button rendering, add onEscape action and self-contained AccountMenu
This commit is contained in:
@@ -10,16 +10,17 @@
|
||||
import GlassMenu from './GlassMenu.svelte';
|
||||
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';
|
||||
|
||||
interface Props {
|
||||
user: SessionUser;
|
||||
accountUrl?: string | null;
|
||||
width?: 'sm' | 'md';
|
||||
extraItems?: Snippet<[{ close: () => void }]>;
|
||||
trigger: Snippet<[{ toggle: () => void; open: boolean }]>;
|
||||
}
|
||||
|
||||
let { user, accountUrl, width = 'sm', extraItems, trigger }: Props = $props();
|
||||
let { user, accountUrl, width = 'sm', extraItems }: Props = $props();
|
||||
|
||||
const WIDTH: Record<'sm' | 'md', string> = { sm: 'w-52', md: 'w-56' };
|
||||
|
||||
@@ -32,16 +33,23 @@
|
||||
function close() {
|
||||
open = false;
|
||||
}
|
||||
|
||||
function onWindowKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={onWindowKeydown} />
|
||||
|
||||
<div class="relative" use:clickOutside={close}>
|
||||
{@render trigger({ toggle, open })}
|
||||
<div class="relative" use:clickOutside={close} use:onEscape={open ? close : undefined}>
|
||||
<button
|
||||
type="button"
|
||||
onclick={toggle}
|
||||
aria-label="Tu cuenta"
|
||||
aria-expanded={open}
|
||||
class="block shrink-0 rounded-full transition active:scale-95"
|
||||
>
|
||||
<Avatar
|
||||
name={user.name}
|
||||
src={user.picture}
|
||||
size="sm"
|
||||
class="border border-line-strong opacity-90"
|
||||
/>
|
||||
</button>
|
||||
|
||||
{#if open}
|
||||
<GlassMenu
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { onEscape } from '$lib/actions/onEscape';
|
||||
import { playlistCover } from '$lib/utils/hrefs';
|
||||
import type { LucideIcon } from '@lucide/svelte';
|
||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||
@@ -18,17 +19,12 @@
|
||||
}
|
||||
|
||||
let { x, y, openLeft, onClose, items, playlistAction, playlists = [] }: Props = $props();
|
||||
|
||||
function onWindowKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') onClose();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={onWindowKeydown} />
|
||||
|
||||
<div
|
||||
class="fixed inset-0 z-(--z-backdrop)"
|
||||
role="presentation"
|
||||
use:onEscape={onClose}
|
||||
onclick={onClose}
|
||||
oncontextmenu={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -28,21 +28,17 @@
|
||||
);
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
{href}
|
||||
{target}
|
||||
rel={target === '_blank' ? 'noopener noreferrer' : undefined}
|
||||
data-sveltekit-reload={reload ? '' : undefined}
|
||||
{onclick}
|
||||
class={classes}
|
||||
>
|
||||
<Icon class="size-icon-md shrink-0" />
|
||||
{label}
|
||||
</a>
|
||||
{:else}
|
||||
<button {type} {onclick} class={classes}>
|
||||
<Icon class="size-icon-md shrink-0" />
|
||||
{label}
|
||||
</button>
|
||||
{/if}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<svelte:element
|
||||
this={href ? 'a' : 'button'}
|
||||
{href}
|
||||
{target}
|
||||
rel={href && target === '_blank' ? 'noopener noreferrer' : undefined}
|
||||
type={href ? undefined : type}
|
||||
data-sveltekit-reload={href && reload ? '' : undefined}
|
||||
{onclick}
|
||||
class={classes}
|
||||
>
|
||||
<Icon class="size-icon-md shrink-0" />
|
||||
{label}
|
||||
</svelte:element>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import IconButton from '../primitives/IconButton.svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { pop } from '$lib/utils/transitions';
|
||||
import { onEscape } from '$lib/actions/onEscape';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
@@ -48,12 +49,7 @@
|
||||
}
|
||||
|
||||
function onWindowKeydown(event: KeyboardEvent) {
|
||||
if (!open) return;
|
||||
if (event.key === 'Escape') {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
if (event.key !== 'Tab') return;
|
||||
if (!open || event.key !== 'Tab') return;
|
||||
const elements = focusableElements();
|
||||
if (elements.length === 0) return;
|
||||
const first = elements[0];
|
||||
@@ -79,6 +75,7 @@
|
||||
>
|
||||
<div
|
||||
bind:this={panel}
|
||||
use:onEscape={onClose}
|
||||
transition:pop={{ duration: 200 }}
|
||||
class="glass-panel max-h-[85dvh] w-full glow-drift overflow-y-auto {maxWidth} rounded-panel p-5 outline-none sm:p-6 {panelClass}"
|
||||
role="dialog"
|
||||
|
||||
Reference in New Issue
Block a user