FIX - migrate Music navigation to full width

This commit is contained in:
DerEchteAlec
2026-08-13 19:45:02 +02:00
parent 7fac1bdbcd
commit b9bf5dd7ef
2 changed files with 146 additions and 57 deletions
@@ -0,0 +1,73 @@
import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
const source = readFileSync(new URL('./MusicApp.vue', import.meta.url), 'utf8')
const navigationSource = source.slice(
source.indexOf('<SkyPillNavigation'),
source.indexOf('</SkyPillNavigation>') + '</SkyPillNavigation>'.length,
)
describe('MusicApp Sky pill navigation contract', () => {
it('uses the full-width sliding Glass navigation outside playlists', () => {
expect(source).not.toContain('kTabbar')
expect(source).not.toContain('kTabbarLink')
expect(source).not.toContain('kToolbarPane')
expect(source).not.toContain('<k-tabbar')
expect(source).not.toContain('<k-toolbar-pane')
expect(navigationSource).toContain('v-if="!activePlaylist"')
expect(navigationSource).toContain('layout="full"')
expect(navigationSource).toContain('<SkySegmented')
expect(navigationSource).toContain('navigation')
expect(navigationSource).toContain(':active-index="activeTabIndex"')
expect(navigationSource).toContain(':data-active-tab="activeTab"')
expect(navigationSource).toContain(':item-count="tabs.length"')
expect(navigationSource).toContain('<SkySegmentedButton')
expect(navigationSource).not.toContain('compact')
})
it('keeps the three localized Music destinations and switching behavior', () => {
expect(source).toContain("{ id: 'library', icon: Library }")
expect(source).toContain("{ id: 'playlists', icon: ListMusic }")
expect(source).toContain("{ id: 'search', icon: Search }")
expect(navigationSource).toContain(':active="activeTab === item.id"')
expect(navigationSource).toContain(
':aria-label="phone.t(`Apps.music.tabs.${item.id}`)"',
)
expect(navigationSource).toContain('@click="selectTab(item.id)"')
expect(navigationSource).toContain(':size="20"')
expect(source).toMatch(
/function selectTab\(tab: MusicTab\): void \{\s+activeTab\.value = tab\s+activePlaylist\.value = null\s+scrollToTop\(\)\s+\}/,
)
})
it('reserves the absolute navigation and mini-player without changing playlist space', () => {
expect(source).toMatch(
/\.music-scroll\s*\{[^}]*padding:\s*8px 0\s*calc\(\s*var\(--sky-safe-area-bottom\) \+ var\(--sky-tabbar-height\) \+\s*var\(--sky-space-3\)\s*\)/s,
)
expect(source).toMatch(
/\.music-app--playing \.music-scroll\s*\{[^}]*padding-bottom:\s*calc\(\s*var\(--music-mini-player-bottom\) \+ var\(--music-mini-player-height\) \+\s*var\(--sky-space-3\)\s*\)/s,
)
expect(source).toContain(
'var(--sky-safe-area-bottom) + var(--sky-tabbar-height) + var(--sky-space-2)',
)
expect(source).toContain('--music-mini-player-height: 58px')
expect(source).toMatch(
/\.music-app--playlist \.music-scroll\s*\{[^}]*padding-bottom:\s*42px/s,
)
expect(source).toMatch(
/\.music-app--playlist\.music-app--playing \.music-scroll\s*\{[^}]*padding-bottom:\s*100px/s,
)
})
it('bridges the Music accent and light-dark theme into Sky UI', () => {
expect(source).toContain('class="music-app sky-ui-provider"')
expect(source).toContain("'sky-ui-provider--dark': phone.isDarkMode")
expect(source).toContain('--sky-app-accent: var(--music-accent)')
expect(source).toContain('class="music-navigation__item"')
expect(source).toMatch(
/\.music-navigation__item > span:last-child\s*\{[^}]*text-overflow:\s*ellipsis[^}]*white-space:\s*nowrap/s,
)
expect(source).not.toContain('.music-tabbar')
})
})
+73 -57
View File
@@ -6,7 +6,6 @@ import {
kDialog,
kDialogButton,
kGlass,
kIcon,
kLink,
kList,
kListButton,
@@ -19,10 +18,7 @@ import {
kRange,
kSearchbar,
kSheet,
kTabbar,
kTabbarLink,
kToast,
kToolbarPane,
} from 'konsta/vue'
import {
Check,
@@ -55,6 +51,7 @@ import type { MusicPlaylist, MusicTrack } from '@/types/music'
import { easyShareMusicTarget } from '@/utils/easyshare'
import { consumeEscape, handleEnterAction } from '@/utils/keyboard'
import { musicEscapeLayer } from '@/utils/musicEscape'
import { SkyPillNavigation, SkySegmented, SkySegmentedButton } from '@/ui'
type MusicTab = 'library' | 'playlists' | 'search'
type MusicSheet =
@@ -75,6 +72,14 @@ const easyShare = useEasyShareStore()
const phone = usePhoneStore()
const route = useRoute()
const activeTab = ref<MusicTab>('library')
const tabs = [
{ id: 'library', icon: Library },
{ id: 'playlists', icon: ListMusic },
{ id: 'search', icon: Search },
] as const
const activeTabIndex = computed(() =>
tabs.findIndex((item) => item.id === activeTab.value),
)
const activePlaylist = ref<MusicPlaylist | null>(null)
const addMenuOpened = ref(false)
const actionMenuOpened = ref(false)
@@ -566,10 +571,11 @@ onBeforeUnmount(() => {
<template>
<k-page
component="main"
class="music-app"
class="music-app sky-ui-provider"
:class="{
'music-app--playlist': activePlaylist,
'music-app--playing': music.currentTrack,
'sky-ui-provider--dark': phone.isDarkMode,
}"
>
<k-navbar
@@ -952,50 +958,41 @@ onBeforeUnmount(() => {
</button>
</k-glass>
<k-tabbar
<SkyPillNavigation
v-if="!activePlaylist"
component="nav"
icons
labels
class="music-tabbar"
:aria-label="phone.t('Apps.music.navigation')"
class="music-navigation"
layout="full"
:label="phone.t('Apps.music.navigation')"
>
<k-toolbar-pane>
<k-tabbar-link
component="button"
:active="activeTab === 'library'"
:link-props="{ type: 'button' }"
@click="selectTab('library')"
<SkySegmented
strong
rounded
navigation
:active-index="activeTabIndex"
:aria-label="phone.t('Apps.music.navigation')"
:data-active-tab="activeTab"
:item-count="tabs.length"
>
<SkySegmentedButton
v-for="item in tabs"
:key="item.id"
:active="activeTab === item.id"
:aria-label="phone.t(`Apps.music.tabs.${item.id}`)"
type="button"
@click="selectTab(item.id)"
>
<template #icon
><k-icon><Library class="w-7 h-7" /></k-icon
></template>
<template #label>{{ phone.t('Apps.music.tabs.library') }}</template>
</k-tabbar-link>
<k-tabbar-link
component="button"
:active="activeTab === 'playlists'"
:link-props="{ type: 'button' }"
@click="selectTab('playlists')"
>
<template #icon
><k-icon><ListMusic class="w-7 h-7" /></k-icon
></template>
<template #label>{{ phone.t('Apps.music.tabs.playlists') }}</template>
</k-tabbar-link>
<k-tabbar-link
component="button"
:active="activeTab === 'search'"
:link-props="{ type: 'button' }"
@click="selectTab('search')"
>
<template #icon
><k-icon><Search class="w-7 h-7" /></k-icon
></template>
<template #label>{{ phone.t('Apps.music.tabs.search') }}</template>
</k-tabbar-link>
</k-toolbar-pane>
</k-tabbar>
<span class="music-navigation__item">
<component
:is="item.icon"
:size="20"
:stroke-width="2"
aria-hidden="true"
/>
<span>{{ phone.t(`Apps.music.tabs.${item.id}`) }}</span>
</span>
</SkySegmentedButton>
</SkySegmented>
</SkyPillNavigation>
<button
v-if="addMenuOpened || actionMenuOpened"
@@ -1458,6 +1455,11 @@ onBeforeUnmount(() => {
<style scoped>
.music-app {
--music-accent: #fa2d48;
--music-mini-player-bottom: calc(
var(--sky-safe-area-bottom) + var(--sky-tabbar-height) + var(--sky-space-2)
);
--music-mini-player-height: 58px;
--sky-app-accent: var(--music-accent);
--music-bg: #f7f7fa;
--music-card: rgb(255 255 255 / 88%);
--music-label: #111114;
@@ -1498,7 +1500,11 @@ onBeforeUnmount(() => {
position: relative;
flex: 1 1 auto;
min-height: 0;
padding: 8px 0 24px;
padding: 8px 0
calc(
var(--sky-safe-area-bottom) + var(--sky-tabbar-height) +
var(--sky-space-3)
);
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior: contain;
@@ -1506,7 +1512,10 @@ onBeforeUnmount(() => {
}
.music-app--playing .music-scroll {
padding-bottom: 82px;
padding-bottom: calc(
var(--music-mini-player-bottom) + var(--music-mini-player-height) +
var(--sky-space-3)
);
}
.music-app--playlist .music-scroll {
@@ -1835,9 +1844,9 @@ onBeforeUnmount(() => {
position: absolute;
z-index: 32;
right: 8px;
bottom: 111px;
bottom: var(--music-mini-player-bottom);
left: 8px;
height: 58px;
height: var(--music-mini-player-height);
padding: 6px 10px 6px 7px;
border-radius: 17px;
display: grid;
@@ -1888,18 +1897,25 @@ onBeforeUnmount(() => {
color: var(--music-label);
}
.music-tabbar {
.music-navigation {
z-index: 31;
flex: 0 0 auto;
}
.music-tabbar :deep(.k-toolbar-pane) {
width: 100% !important;
.music-navigation__item {
min-width: 0;
max-width: 100%;
display: flex;
align-items: center;
flex-direction: column;
gap: 2px;
line-height: 1;
}
.music-tabbar :deep(.k-toolbar-pane > .k-link) {
min-width: 0 !important;
flex: 1 1 33.333%;
.music-navigation__item > span:last-child {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.music-form-sheet,