ui: keep caret shadow behind menu panel

cx
This commit is contained in:
vicrodh committed 2026-01-22 12:20:59 -06:00
1 parent df9798c8d8
commit 9cf345921e
1 file changed
+162 -149
+162 -149
View File
@@ -301,175 +301,177 @@
style={menuStyle} style={menuStyle}
use:portal use:portal
> >
{#if hasPlayback} <div class="menu-panel">
{#if onPlayNow} {#if hasPlayback}
<button class="menu-item" onclick={() => handleAction(onPlayNow)}> {#if onPlayNow}
<Play size={14} /> <button class="menu-item" onclick={() => handleAction(onPlayNow)}>
<span>Play now</span> <Play size={14} />
</button> <span>Play now</span>
{/if} </button>
{#if onPlayTrackOnly}
<button class="menu-item" onclick={() => handleAction(onPlayTrackOnly)}>
<Play size={14} />
<span>Play track only</span>
</button>
{/if}
{#if onPlayFromHere}
<button class="menu-item" onclick={() => handleAction(onPlayFromHere)}>
<Play size={14} />
<span>Play from here</span>
</button>
{/if}
{#if onPlayNext}
<button class="menu-item" onclick={() => handleAction(onPlayNext)}>
<ListPlus size={14} />
<span>Play next</span>
</button>
{/if}
{#if onPlayLater}
<button class="menu-item" onclick={() => handleAction(onPlayLater)}>
<ListEnd size={14} />
<span>Play later</span>
</button>
{/if}
{#if onCreateRadio}
<button class="menu-item" onclick={() => handleAction(onCreateRadio)}>
<Radio size={14} />
<span>Create radio</span>
</button>
{/if}
{/if}
{#if hasPlayback && (hasLibrary || hasShare || hasNav || hasDownload)}
<div class="separator"></div>
{/if}
{#if hasLibrary}
{#if onAddFavorite}
<button class="menu-item" onclick={() => handleAction(onAddFavorite)}>
<Heart size={14} />
<span>Add to favorites</span>
</button>
{/if}
{#if onAddToPlaylist && !shouldHidePlaylistFeatures()}
<button class="menu-item" onclick={() => handleAction(onAddToPlaylist)}>
<ListMusic size={14} />
<span>Add to playlist</span>
</button>
{/if}
{#if onRemoveFromPlaylist}
<button class="menu-item danger" onclick={() => handleAction(onRemoveFromPlaylist)}>
<Trash2 size={14} />
<span>Remove from playlist</span>
</button>
{/if}
{/if}
{#if hasLibrary && (hasShare || hasNav)}
<div class="separator"></div>
{/if}
{#if hasShare}
<div
class="menu-item submenu-trigger"
bind:this={shareTriggerRef}
onmouseenter={() => {
shareOpen = true;
setSubmenuPosition();
}}
onclick={() => {
shareOpen = !shareOpen;
if (shareOpen) setSubmenuPosition();
}}
>
<Share2 size={14} />
<span>Share</span>
<ChevronRight size={14} class="chevron" />
{#if shareOpen}
<div class="submenu" bind:this={submenuEl} style={submenuStyle}>
{#if onShareQobuz}
<button class="menu-item" onclick={() => handleAction(onShareQobuz)}>
<Link size={14} />
<span>Qobuz™ link</span>
</button>
{/if}
{#if onShareSonglink}
<button class="menu-item" onclick={() => handleAction(onShareSonglink)}>
<Link size={14} />
<span>Song.link</span>
</button>
{/if}
</div>
{/if} {/if}
</div> {#if onPlayTrackOnly}
{/if} <button class="menu-item" onclick={() => handleAction(onPlayTrackOnly)}>
<Play size={14} />
<span>Play track only</span>
</button>
{/if}
{#if onPlayFromHere}
<button class="menu-item" onclick={() => handleAction(onPlayFromHere)}>
<Play size={14} />
<span>Play from here</span>
</button>
{/if}
{#if onPlayNext}
<button class="menu-item" onclick={() => handleAction(onPlayNext)}>
<ListPlus size={14} />
<span>Play next</span>
</button>
{/if}
{#if onPlayLater}
<button class="menu-item" onclick={() => handleAction(onPlayLater)}>
<ListEnd size={14} />
<span>Play later</span>
</button>
{/if}
{#if onCreateRadio}
<button class="menu-item" onclick={() => handleAction(onCreateRadio)}>
<Radio size={14} />
<span>Create radio</span>
</button>
{/if}
{/if}
{#if (hasShare || hasLibrary) && hasDownload} {#if hasPlayback && (hasLibrary || hasShare || hasNav || hasDownload)}
<div class="separator"></div> <div class="separator"></div>
{/if} {/if}
{#if hasDownload} {#if hasLibrary}
{#if isTrackDownloaded} {#if onAddFavorite}
<button class="menu-item" onclick={() => handleAction(onAddFavorite)}>
<Heart size={14} />
<span>Add to favorites</span>
</button>
{/if}
{#if onAddToPlaylist && !shouldHidePlaylistFeatures()}
<button class="menu-item" onclick={() => handleAction(onAddToPlaylist)}>
<ListMusic size={14} />
<span>Add to playlist</span>
</button>
{/if}
{#if onRemoveFromPlaylist}
<button class="menu-item danger" onclick={() => handleAction(onRemoveFromPlaylist)}>
<Trash2 size={14} />
<span>Remove from playlist</span>
</button>
{/if}
{/if}
{#if hasLibrary && (hasShare || hasNav)}
<div class="separator"></div>
{/if}
{#if hasShare}
<div <div
class="menu-item submenu-trigger" class="menu-item submenu-trigger"
bind:this={downloadTriggerRef} bind:this={shareTriggerRef}
onmouseenter={() => { onmouseenter={() => {
downloadOpen = true; shareOpen = true;
shareOpen = false; setSubmenuPosition();
setDownloadSubmenuPosition();
}} }}
onclick={() => { onclick={() => {
downloadOpen = !downloadOpen; shareOpen = !shareOpen;
shareOpen = false; if (shareOpen) setSubmenuPosition();
if (downloadOpen) setDownloadSubmenuPosition();
}} }}
> >
<CloudDownload size={14} /> <Share2 size={14} />
<span>Save for offline</span> <span>Share</span>
<ChevronRight size={14} class="chevron" /> <ChevronRight size={14} class="chevron" />
{#if downloadOpen} {#if shareOpen}
<div class="submenu" bind:this={downloadSubmenuEl} style={downloadSubmenuStyle}> <div class="submenu" bind:this={submenuEl} style={submenuStyle}>
{#if onReDownload} {#if onShareQobuz}
<button class="menu-item" onclick={() => handleAction(onReDownload)}> <button class="menu-item" onclick={() => handleAction(onShareQobuz)}>
<RefreshCw size={14} /> <Link size={14} />
<span>Refresh offline copy</span> <span>Qobuz™ link</span>
</button> </button>
{/if} {/if}
{#if onRemoveDownload} {#if onShareSonglink}
<button class="menu-item danger" onclick={() => handleAction(onRemoveDownload)}> <button class="menu-item" onclick={() => handleAction(onShareSonglink)}>
<Trash2 size={14} /> <Link size={14} />
<span>Remove offline copy</span> <span>Song.link</span>
</button> </button>
{/if} {/if}
</div> </div>
{/if} {/if}
</div> </div>
{:else}
<button class="menu-item" onclick={() => handleAction(onDownload)}>
<CloudDownload size={14} />
<span>Save for offline</span>
</button>
{/if} {/if}
{/if}
{#if hasDownload && hasNav} {#if (hasShare || hasLibrary) && hasDownload}
<div class="separator"></div> <div class="separator"></div>
{/if} {/if}
{#if hasNav} {#if hasDownload}
{#if onGoToAlbum} {#if isTrackDownloaded}
<button class="menu-item" onclick={() => handleAction(onGoToAlbum)}> <div
<Disc3 size={14} /> class="menu-item submenu-trigger"
<span>Go to album</span> bind:this={downloadTriggerRef}
</button> onmouseenter={() => {
downloadOpen = true;
shareOpen = false;
setDownloadSubmenuPosition();
}}
onclick={() => {
downloadOpen = !downloadOpen;
shareOpen = false;
if (downloadOpen) setDownloadSubmenuPosition();
}}
>
<CloudDownload size={14} />
<span>Save for offline</span>
<ChevronRight size={14} class="chevron" />
{#if downloadOpen}
<div class="submenu" bind:this={downloadSubmenuEl} style={downloadSubmenuStyle}>
{#if onReDownload}
<button class="menu-item" onclick={() => handleAction(onReDownload)}>
<RefreshCw size={14} />
<span>Refresh offline copy</span>
</button>
{/if}
{#if onRemoveDownload}
<button class="menu-item danger" onclick={() => handleAction(onRemoveDownload)}>
<Trash2 size={14} />
<span>Remove offline copy</span>
</button>
{/if}
</div>
{/if}
</div>
{:else}
<button class="menu-item" onclick={() => handleAction(onDownload)}>
<CloudDownload size={14} />
<span>Save for offline</span>
</button>
{/if}
{/if} {/if}
{#if onGoToArtist}
<button class="menu-item" onclick={() => handleAction(onGoToArtist)}> {#if hasDownload && hasNav}
<User size={14} /> <div class="separator"></div>
<span>Go to artist</span>
</button>
{/if} {/if}
{/if}
{#if hasNav}
{#if onGoToAlbum}
<button class="menu-item" onclick={() => handleAction(onGoToAlbum)}>
<Disc3 size={14} />
<span>Go to album</span>
</button>
{/if}
{#if onGoToArtist}
<button class="menu-item" onclick={() => handleAction(onGoToArtist)}>
<User size={14} />
<span>Go to artist</span>
</button>
{/if}
{/if}
</div>
</div> </div>
{/if} {/if}
</div> </div>
@@ -504,12 +506,17 @@
.menu { .menu {
position: fixed; position: fixed;
min-width: 160px; min-width: 160px;
z-index: 99999;
overflow: visible;
}
.menu-panel {
position: relative;
z-index: 2;
background-color: var(--bg-tertiary); background-color: var(--bg-tertiary);
border-radius: 8px; border-radius: 8px;
padding: 2px 0; padding: 2px 0;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
z-index: 99999;
overflow: visible;
} }
/* Caret outside the menu pointing at the trigger */ /* Caret outside the menu pointing at the trigger */
@@ -536,7 +543,7 @@
border-right: 9px solid var(--bg-tertiary); border-right: 9px solid var(--bg-tertiary);
} }
/* Shadow for caret (only outward, never over the menu) */ /* Shadow for caret (only outward, never over the panel) */
.menu.open-left::before, .menu.open-left::before,
.menu.open-right::before { .menu.open-right::before {
content: ''; content: '';
@@ -550,6 +557,7 @@
pointer-events: none; pointer-events: none;
opacity: 0.9; opacity: 0.9;
filter: blur(2px); filter: blur(2px);
z-index: 0;
} }
.menu.open-left::before { .menu.open-left::before {
@@ -564,6 +572,11 @@
border-right: 10px solid rgba(0, 0, 0, 0.9); border-right: 10px solid rgba(0, 0, 0, 0.9);
} }
.menu.open-left::after,
.menu.open-right::after {
z-index: 1;
}
.menu-item { .menu-item {
width: 100%; width: 100%;
padding: 8px 12px; padding: 8px 12px;