feat(phone): unify popups and add dynamic island

This commit is contained in:
Leon.Schmidt
2026-08-17 22:19:43 +02:00
parent fc3f5b1da2
commit 29fb234d5c
34 changed files with 1397 additions and 211 deletions
+4 -16
View File
@@ -13,6 +13,7 @@ import { useRoute, useRouter } from 'vue-router'
import { SkyProvider } from '@/ui'
import PhoneHomeIndicator from '@/components/PhoneHomeIndicator.vue'
import PhoneControlCenter from '@/components/PhoneControlCenter.vue'
import PhoneDynamicIsland from '@/components/PhoneDynamicIsland.vue'
import PhoneMediaCapture from '@/components/PhoneMediaCapture.vue'
import PhoneMemoRecorder from '@/components/PhoneMemoRecorder.vue'
import PhoneLockScreen from '@/components/PhoneLockScreen.vue'
@@ -316,9 +317,6 @@ const DARK_STATUS_BAR_APP_IDS = new Set([
const isDevelopmentRoute = computed(
() => isDevelopment && route.name === 'development-sky-ui',
)
const showActiveCallReturn = computed(
() => Boolean(calls.activeCall) && route.params.appId !== 'phone',
)
const appTransitionName = computed(() =>
route.query.transition === 'app-switch' ? 'app-switch' : 'app-window',
)
@@ -1084,7 +1082,6 @@ function onMessage(event: MessageEvent<AppMessage>): void {
isUnlocking.value = false
loadUnlockedPhoneData()
}
window.setTimeout(() => void router.push('/apps/phone'), 0)
} else if (event.data?.type === 'sim:picker' && event.data.data) {
simPicker.value = event.data.data as unknown as SimPickerPayload
} else if (event.data?.type === 'sim:picker-close') {
@@ -1330,12 +1327,6 @@ function toggleHardwareAlertMute(): void {
showHardwareVolumeHud()
}
function returnToActiveCall(): void {
if (!calls.activeCall) return
controlCenterOpened.value = false
void router.push('/apps/phone')
}
function unlockCamera(): void {
if (phone.security.enabled) {
pendingUnlockRoute.value = '/apps/camera'
@@ -1414,9 +1405,7 @@ onMounted(() => {
window.setTimeout(() => {
notifications.show({
appId: 'messages',
persistent: !developmentParameters.has(
'mutedNotificationPreview',
),
persistent: !developmentParameters.has('mutedNotificationPreview'),
route: '/apps/messages',
text: 'You still got that spare alternator?',
title: 'Tommy V',
@@ -1570,7 +1559,7 @@ onBeforeUnmount(() => {
@open="openNotificationPreview"
/>
<div
v-if="phone.isOpen || notifications.current"
v-if="phone.isOpen || notifications.current || calls.activeCall"
class="phone-resolution-wrapper phone-resolution-wrapper--primary"
>
<div class="phone-resolution-canvas phone-resolution-canvas--primary">
@@ -1682,14 +1671,13 @@ onBeforeUnmount(() => {
>
<PhoneStatusBar
v-if="!isLocked && !(isHomeRoute && springboardEditing)"
:active-call-return="showActiveCallReturn"
:control-center-opened="controlCenterOpened"
:interactive="!setupRequired"
:lockable="!setupRequired"
@active-call="returnToActiveCall"
@control-center="toggleControlCenter"
@lock="lockPhone"
/>
<PhoneDynamicIsland v-if="!setupRequired" />
<SpringboardView
v-if="!isDevelopmentRoute && !setupRequired"
@edit-mode-change="springboardEditing = $event"
@@ -0,0 +1,87 @@
import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
const source = readFileSync(
new URL('./PhoneDynamicIsland.vue', import.meta.url),
'utf8',
)
const appSource = readFileSync(new URL('../App.vue', import.meta.url), 'utf8')
const recorderSource = readFileSync(
new URL('./PhoneMemoRecorder.vue', import.meta.url),
'utf8',
)
const memosSource = readFileSync(
new URL('../views/apps/MemosApp.vue', import.meta.url),
'utf8',
)
const clockSource = readFileSync(
new URL('../views/apps/ClockApp.vue', import.meta.url),
'utf8',
)
describe('Phone Dynamic Island contract', () => {
it('renders once at phone shell level instead of forcing calls into Phone', () => {
expect(appSource).toContain(
"import PhoneDynamicIsland from '@/components/PhoneDynamicIsland.vue'",
)
expect(appSource).toContain('<PhoneDynamicIsland v-if="!setupRequired" />')
expect(appSource).toContain(
'phone.isOpen || notifications.current || calls.activeCall',
)
expect(appSource).not.toContain(
"window.setTimeout(() => void router.push('/apps/phone'), 0)",
)
})
it('prioritizes calls and exposes answer, decline, and hangup controls', () => {
expect(source.indexOf("return 'incoming-call'")).toBeLessThan(
source.indexOf("return 'recording'"),
)
expect(source).toContain('@click.stop="calls.answer()"')
expect(source).toContain('@click.stop="endCall"')
expect(source).toContain('void calls.decline()')
expect(source).toContain('void calls.hangup()')
expect(source).toContain('call.answeredAt ?? call.startedAt')
})
it('connects music, recorder, timer, and stopwatch controls to their stores', () => {
expect(source).toContain('@click.stop="music.previous()"')
expect(source).toContain('@click.stop="music.toggle()"')
expect(source).toContain('@click.stop="music.next()"')
expect(source).toContain("postRecorderCommand('memo:recordStop')")
expect(source).toContain('clock.pauseTimer(Date.now())')
expect(source).toContain('clock.pauseStopwatch(Date.now())')
expect(source).toContain('clock.addLap(Date.now())')
})
it('keeps recorder state available across app changes', () => {
expect(recorderSource).toContain(
"message.type === 'memo:recordStateRequest'",
)
expect(memosSource).toContain(
"postRecorderCommand('memo:recordStateRequest')",
)
expect(memosSource).not.toContain(
"if (recordingActive.value) postRecorderCommand('memo:recordCancel')",
)
})
it('opens both clock live activities on the correct clock tab', () => {
expect(source).toContain("'/apps/clock?section=timer'")
expect(source).toContain("'/apps/clock?section=stopwatch'")
expect(clockSource).toContain(
"section === 'timer' || section === 'stopwatch'",
)
})
it('animates state changes and moves popup notifications below expanded UI', () => {
expect(source).toContain('<Transition name="phone-dynamic-island">')
expect(source).toContain(
'<Transition name="phone-dynamic-island-content" mode="out-in">',
)
expect(source).toContain(".phone-dynamic-island[data-expanded='true']")
expect(source).toContain('~ .phone-notification-provider')
expect(source).toContain('@media (prefers-reduced-motion: reduce)')
})
})
File diff suppressed because it is too large Load Diff
@@ -557,6 +557,8 @@ function onMessage(event: MessageEvent): void {
void stopRecording(data)
} else if (message.type === 'memo:recordCancel') {
cancelRecording()
} else if (message.type === 'memo:recordStateRequest') {
postRecorderState(currentState)
} else if (message.type === 'memos:uploadReady') {
void uploadReady(data as MemoUploadReady)
} else if (message.type === 'memos:uploadResult') {
@@ -42,7 +42,7 @@ describe('Banking app Sky UI migration', () => {
'SkyButton',
'SkySpinner',
'SkyEmptyState',
'SkyToast',
'SkyNotification',
]) {
expect(source).toContain(`<${component}`)
}
+4 -7
View File
@@ -44,7 +44,7 @@ import {
SkySpinner,
SkyTabBar,
SkyTabButton,
SkyToast,
SkyNotification,
} from '@/ui'
type BankingTab = 'home' | 'activity'
@@ -696,13 +696,10 @@ onBeforeUnmount(() => {
</section>
</SkySheet>
<SkyToast
<SkyNotification
:opened="cooldownToastOpened"
position="center"
vertical-position="bottom"
:text="errorMessage('reload_cooldown')"
@click="closeCooldownToast"
>
{{ errorMessage('reload_cooldown') }}
</SkyToast>
/>
</SkyAppPage>
</template>
@@ -29,7 +29,7 @@ describe('Billing app Sky UI migration', () => {
'SkyTabBar',
'SkyTabButton',
'SkySheet',
'SkyToast',
'SkyNotification',
]) {
expect(source).toContain(`<${component}`)
}
+7 -5
View File
@@ -47,7 +47,7 @@ import {
SkySpinner,
SkyTabBar,
SkyTabButton,
SkyToast,
SkyNotification,
} from '@/ui'
import { isTrustedRootMessageSource } from '@/utils/windowMessages'
@@ -771,9 +771,11 @@ onBeforeUnmount(() => {
</section>
</SkySheet>
<SkyToast :opened="toastOpen" position="center" class="billing-toast">
{{ toastText }}
</SkyToast>
<SkyNotification
:opened="toastOpen"
:text="toastText"
class="billing-notification"
/>
</SkyAppPage>
</template>
@@ -1523,7 +1525,7 @@ onBeforeUnmount(() => {
var(--billing-panel);
}
}
.billing-toast {
.billing-notification {
z-index: 50;
}
</style>
+3 -31
View File
@@ -38,6 +38,7 @@ import {
SkyButton,
SkyGlass,
SkyIcon,
SkyNotification,
SkyNavbar,
SkyAppPage,
SkySearchbar,
@@ -2113,11 +2114,7 @@ onMounted(async () => {
app-id="citymarkt"
:app-name="phone.t('Apps.citymarkt.name')"
/>
<Transition name="toast"
><div v-if="feedback" class="citymarkt__toast">
{{ feedback }}
</div></Transition
>
<SkyNotification :opened="Boolean(feedback)" :text="feedback" />
</sky-app-page>
</template>
@@ -3014,30 +3011,6 @@ onMounted(async () => {
background: var(--panel);
color: #ff796f;
}
.citymarkt__toast {
position: absolute;
z-index: 20;
right: 18px;
bottom: 92px;
left: 18px;
padding: 10px 12px;
border-radius: 11px;
background: #f4f4ee;
color: #171816;
box-shadow: 0 8px 30px #0007;
font-size: 10px;
font-weight: 800;
text-align: center;
}
.toast-enter-active,
.toast-leave-active {
transition: 0.2s;
}
.toast-enter-from,
.toast-leave-to {
transform: translateY(8px);
opacity: 0;
}
.citymarkt {
--color-primary: var(--yellow);
position: relative;
@@ -3740,8 +3713,7 @@ onMounted(async () => {
font-size: 10.5px;
}
.citymarkt__meta,
.citymarkt__inline-auth,
.citymarkt__toast {
.citymarkt__inline-auth {
font-size: 12px;
}
.citymarkt__composer textarea,
+4 -2
View File
@@ -47,7 +47,9 @@ const phone = usePhoneStore()
const clock = useClockStore()
const route = useRoute()
const tab = ref<'world' | 'alarm' | 'stopwatch' | 'timer'>(
route.query.section === 'timer' ? 'timer' : 'world',
route.query.section === 'timer' || route.query.section === 'stopwatch'
? route.query.section
: 'world',
)
const alarmEditor = ref<
{ mode: 'create' } | { id: string; mode: 'edit' } | null
@@ -205,7 +207,7 @@ watch([() => clock.stopwatchStartedAt, tab], ([startedAt, activeTab]) => {
watch(
() => route.query.section,
(section) => {
if (section === 'timer') tab.value = 'timer'
if (section === 'timer' || section === 'stopwatch') tab.value = section
},
)
+4 -7
View File
@@ -37,7 +37,7 @@ import {
SkyScrollRail,
SkySection,
SkySheet,
SkyToast,
SkyNotification,
SkyToggle,
SkyToolbarPane,
} from '@/ui'
@@ -2599,14 +2599,11 @@ onBeforeUnmount(() => {
</template>
</SkyDialog>
<SkyToast
<SkyNotification
:opened="toastOpened"
position="center"
vertical-position="center"
:text="toastText"
@click="toastOpened = false"
>
{{ toastText }}
</SkyToast>
/>
</SkyAppPage>
</template>
+2 -4
View File
@@ -17,7 +17,7 @@ import {
SkySheet,
SkyTabBar,
SkyTabButton,
SkyToast,
SkyNotification,
SkyToggle,
SkyToolbarPane,
} from '@/ui'
@@ -2352,9 +2352,7 @@ onBeforeUnmount(() => {
>
</sky-dialog>
<sky-toast :opened="Boolean(toastText)" position="center">{{
toastText
}}</sky-toast>
<sky-notification :opened="Boolean(toastText)" :text="toastText" />
</sky-app-page>
</template>
+6 -9
View File
@@ -72,7 +72,7 @@ import {
SkySettingsRow,
SkySheet,
SkySpinner,
SkyToast,
SkyNotification,
} from '@/ui'
const VOICE_MAX_DURATION_MS = 60_000
@@ -1896,7 +1896,7 @@ onBeforeUnmount(() => {
margin: 12px 0 8px;
}
.dc-toast {
.dc-notification {
z-index: 150;
}
@@ -2852,13 +2852,10 @@ onBeforeUnmount(() => {
</template>
</SkyDialog>
<SkyToast
<SkyNotification
:opened="Boolean(toast)"
position="center"
vertical-position="center"
class="dc-toast"
>
{{ toast }}
</SkyToast>
:text="toast"
class="dc-notification"
/>
</SkyAppPage>
</template>
+2 -2
View File
@@ -56,7 +56,7 @@ import {
SkySegmented,
SkySegmentedButton,
SkySheet,
SkyToast,
SkyNotification,
SkyToggle,
SkyMessagebar,
} from '@/ui'
@@ -2334,7 +2334,7 @@ onMounted(async () => {
</template>
</div>
<SkyToast :opened="Boolean(feedback)">{{ feedback }}</SkyToast>
<SkyNotification :opened="Boolean(feedback)" :text="feedback" />
</SkyAppPage>
</template>
+2 -4
View File
@@ -26,7 +26,7 @@ import {
SkyTabBar,
SkyTabButton,
SkyToolbarPane,
SkyToast,
SkyNotification,
SkyToggle,
} from '@/ui'
import {
@@ -2237,9 +2237,7 @@ onBeforeUnmount(() => {
</template>
</sky-dialog>
<sky-toast :opened="Boolean(actionToast)" position="center">
{{ actionToast }}
</sky-toast>
<sky-notification :opened="Boolean(actionToast)" :text="actionToast" />
</sky-app-page>
</template>
+4 -6
View File
@@ -73,7 +73,7 @@ import {
SkySegmentedButton,
SkySheet,
SkySpinner,
SkyToast,
SkyNotification,
SkyToggle,
} from '@/ui'
import { nuiCall } from '@/utils/nui'
@@ -3361,13 +3361,11 @@ onBeforeUnmount(() => {
</SkyDialogButton>
</template>
</SkyDialog>
<SkyToast
<SkyNotification
:opened="Boolean(feedback)"
position="center"
:text="feedback"
@click="feedback = ''"
>
{{ feedback }}
</SkyToast>
/>
</SkyAppPage>
</template>
+4 -6
View File
@@ -11,7 +11,7 @@ import {
SkySpinner,
SkySegmented,
SkySegmentedButton,
SkyToast,
SkyNotification,
} from '@/ui'
import {
ChevronLeft,
@@ -1625,13 +1625,11 @@ onBeforeUnmount(() => {
</template>
</sky-dialog>
<sky-toast
<sky-notification
:opened="toastOpened"
position="center"
:text="toastText"
@click="toastOpened = false"
>
{{ toastText }}
</sky-toast>
/>
</template>
<style scoped>
+4 -7
View File
@@ -42,7 +42,7 @@ import {
SkySegmentedButton,
SkySheet,
SkySpinner,
SkyToast,
SkyNotification,
} from '@/ui'
type GarageFilter = 'all' | GarageVehicleStatus
@@ -570,14 +570,11 @@ onBeforeUnmount(() => {
</template>
</SkyDialog>
<SkyToast
<SkyNotification
:opened="toastOpened"
position="center"
vertical-position="center"
:text="toastText"
@click="toastOpened = false"
>
{{ toastText }}
</SkyToast>
/>
</SkyAppPage>
</template>
+4 -7
View File
@@ -38,7 +38,7 @@ import {
SkyNavbar,
SkySheet,
SkySpinner,
SkyToast,
SkyNotification,
} from '@/ui'
const phone = usePhoneStore()
@@ -572,14 +572,11 @@ onBeforeUnmount(() => {
</template>
</sky-dialog>
<SkyToast
<SkyNotification
:opened="toastOpened"
position="center"
vertical-position="bottom"
:text="toastText"
@click="toastOpened = false"
>
{{ toastText }}
</SkyToast>
/>
</sky-app-page>
</template>
+2 -32
View File
@@ -24,6 +24,7 @@ import {
SkyButton,
SkyGlass,
SkyNavbar,
SkyNotification,
SkyAppPage,
SkyPillNavigation,
SkySearchbar,
@@ -1507,11 +1508,7 @@ onMounted(async () => {
:app-name="phone.t('Apps.localPages.name')"
@logged-out="tab = 'profile'"
/>
<Transition name="toast"
><div v-if="feedback" class="pages__toast">
{{ feedback }}
</div></Transition
>
<SkyNotification :opened="Boolean(feedback)" :text="feedback" />
</sky-app-page>
</template>
@@ -2189,30 +2186,6 @@ onMounted(async () => {
font-style: normal;
font-weight: 900;
}
.pages__toast {
position: absolute;
z-index: 20;
right: 17px;
bottom: 91px;
left: 17px;
padding: 11px;
border-radius: 11px;
background: #fff6cf;
color: #1b2023;
box-shadow: 0 8px 30px #0007;
font-size: 10px;
font-weight: 800;
text-align: center;
}
.toast-enter-active,
.toast-leave-active {
transition: 0.2s;
}
.toast-enter-from,
.toast-leave-to {
transform: translateY(8px);
opacity: 0;
}
.pages__header {
height: 64px;
padding: 6px 16px 8px;
@@ -2738,9 +2711,6 @@ onMounted(async () => {
.pages__photo-picker i {
font-size: 10px;
}
.pages__toast {
font-size: 12px;
}
.pages {
--color-primary: var(--yellow);
position: relative;
+4 -6
View File
@@ -12,7 +12,7 @@ import {
SkyAppPage,
SkySheet,
SkySpinner,
SkyToast,
SkyNotification,
SkyToolbar,
SkyToolbarPane,
} from '@/ui'
@@ -2046,13 +2046,11 @@ onBeforeUnmount(() => {
</template>
</sky-dialog>
<sky-toast
<sky-notification
:opened="toastOpened"
position="center"
:text="toastText"
@click="toastOpened = false"
>
{{ toastText }}
</sky-toast>
/>
</template>
<style scoped>
+2 -4
View File
@@ -8,7 +8,7 @@ import {
SkyGlass,
SkySpinner,
SkySheet,
SkyToast,
SkyNotification,
} from '@/ui'
import {
LocateFixed,
@@ -850,9 +850,7 @@ onBeforeUnmount(() => {
</sky-sheet>
</div>
<sky-toast :opened="Boolean(toastText)" position="center">
{{ toastText }}
</sky-toast>
<sky-notification :opened="Boolean(toastText)" :text="toastText" />
</SkyAppPage>
</template>
+11 -2
View File
@@ -412,6 +412,13 @@ function onMessage(event: MessageEvent): void {
const state = message.data as MemoRecorderState
if (!state || !Array.isArray(state.levels)) return
recorderState.value = state
if (
['paused', 'recording', 'starting', 'stopping', 'uploading'].includes(
state.state,
)
) {
view.value = 'recording'
}
if (state.error) showNotification(recorderError(state.error))
} else if (message.type === 'memo:saved') {
const memo = message.data as MemoDto
@@ -423,12 +430,14 @@ function onMessage(event: MessageEvent): void {
}
}
onMounted(() => window.addEventListener('message', onMessage))
onMounted(() => {
window.addEventListener('message', onMessage)
postRecorderCommand('memo:recordStateRequest')
})
onBeforeUnmount(() => {
window.removeEventListener('message', onMessage)
stopPlayback()
if (recordingActive.value) postRecorderCommand('memo:recordCancel')
if (view.value === 'detail') void persistSelected()
})
</script>
@@ -308,8 +308,11 @@ describe('MessagesApp Sky UI contract', () => {
const contactProfile = source.slice(contactProfileStart, threadScrollStart)
const blockDialogOpened = source.indexOf(':opened="blockDialogOpened"')
const blockDialogStart = source.lastIndexOf('<SkyDialog', blockDialogOpened)
const toastStart = source.indexOf('<SkyToast', blockDialogStart)
const blockDialog = source.slice(blockDialogStart, toastStart)
const notificationStart = source.indexOf(
'<SkyNotification',
blockDialogStart,
)
const blockDialog = source.slice(blockDialogStart, notificationStart)
expect(contactProfile).toContain('kind="action"')
expect(contactProfile).toContain('tone="danger"')
+4 -6
View File
@@ -75,7 +75,7 @@ import {
SkySettingsRow,
SkySheet,
SkySpinner,
SkyToast,
SkyNotification,
SkyToolbar,
} from '@/ui'
@@ -1964,13 +1964,11 @@ onBeforeUnmount(() => {
</template>
</SkyDialog>
<SkyToast
<SkyNotification
:opened="toastOpened"
position="center"
:text="toastText"
@click="toastOpened = false"
>
{{ toastText }}
</SkyToast>
/>
</template>
<style scoped>
+2 -4
View File
@@ -20,7 +20,7 @@ import {
SkyRange,
SkySearchbar,
SkySheet,
SkyToast,
SkyNotification,
} from '@/ui'
import {
Check,
@@ -1479,9 +1479,7 @@ onBeforeUnmount(() => {
</template>
</sky-dialog>
<sky-toast position="center" :opened="Boolean(toastText)">{{
toastText
}}</sky-toast>
<sky-notification :opened="Boolean(toastText)" :text="toastText" />
</sky-app-page>
</template>
+2 -7
View File
@@ -72,7 +72,7 @@ import {
SkySegmentedButton,
SkySheet,
SkySpinner,
SkyToast,
SkyNotification,
SkyToggle,
} from '@/ui'
@@ -2498,12 +2498,7 @@ onBeforeUnmount(() => {
}}</SkyDialogButton></template
></SkyDialog
>
<SkyToast
:opened="Boolean(feedback)"
position="center"
vertical-position="center"
>{{ feedback }}</SkyToast
>
<SkyNotification :opened="Boolean(feedback)" :text="feedback" />
</SkyAppPage>
</template>
@@ -21,7 +21,7 @@ describe('RadioApp Sky UI contract', () => {
expect(source).toContain('<SkyRange')
expect(source).toContain('<SkySettingsGroup')
expect(source).toContain('<SkySettingsRow')
expect(source).toContain('<SkyToast')
expect(source).toContain('<SkyNotification')
})
it('renders Radio and Settings in full-width bottom Glass navigation', () => {
@@ -186,6 +186,7 @@ describe('RadioApp Sky UI contract', () => {
expect(source).toContain('<SkyEmptyState')
expect(source).toContain('role="alert"')
expect(source).toContain(':opened="Boolean(feedback)"')
expect(source).toContain('vertical-position="center"')
expect(source).toContain(':text="feedback"')
expect(source).not.toContain('vertical-position=')
})
})
+4 -7
View File
@@ -30,7 +30,7 @@ import {
SkySettingsRow,
SkySpinner,
SkyStatusCard,
SkyToast,
SkyNotification,
SkyToggle,
} from '@/ui'
import { isTrustedRootMessageSource } from '@/utils/windowMessages'
@@ -537,14 +537,11 @@ onBeforeUnmount(() => {
</SkySegmented>
</SkyPillNavigation>
<SkyToast
<SkyNotification
:opened="Boolean(feedback)"
position="center"
vertical-position="center"
:text="feedback"
@click="feedback = ''"
>
{{ feedback }}
</SkyToast>
/>
</SkyAppPage>
</template>
+4 -7
View File
@@ -74,7 +74,7 @@ import {
SkySettingsRangeRow,
SkySettingsRow,
SkySpinner,
SkyToast,
SkyNotification,
} from '@/ui'
import {
APPEARANCE_MODE_IDS,
@@ -1863,14 +1863,11 @@ onBeforeUnmount(() => {
</template>
</SkyDialog>
<SkyToast
<SkyNotification
:opened="Boolean(accountToast)"
position="center"
vertical-position="center"
:text="accountToast"
@click="accountToast = ''"
>
{{ accountToast }}
</SkyToast>
/>
</SkyAppPage>
</template>
+2 -4
View File
@@ -75,7 +75,7 @@ import {
SkySpinner as kPreloader,
SkyTabBar as kTabbar,
SkyTabButton as kTabbarLink,
SkyToast as kToast,
SkyNotification as kNotification,
SkyToggle as kToggle,
} from '@/ui'
@@ -1747,9 +1747,7 @@ onBeforeUnmount(() => {
>
</k-dialog>
<k-toast :opened="Boolean(toastText)" position="center">{{
toastText
}}</k-toast>
<k-notification :opened="Boolean(toastText)" :text="toastText" />
</k-page>
</template>
@@ -83,4 +83,11 @@ describe('phone apps use Sky UI', () => {
expect(source, file).not.toMatch(/\b:colors=/)
}
})
it('uses top notifications instead of app-level toasts', () => {
for (const { file, source } of appSources) {
expect(source, file).not.toMatch(/\bSkyToast\b|<sky-toast|<k-toast/)
expect(source, file).not.toMatch(/(?:citymarkt|pages)__toast/)
}
})
})
+4 -7
View File
@@ -22,7 +22,7 @@ import {
SkyNavbar,
SkyScrollArea,
SkySpinner,
SkyToast,
SkyNotification,
} from '@/ui'
const phone = usePhoneStore()
@@ -283,14 +283,11 @@ watch(
</SkyLink>
</div>
<SkyToast
<SkyNotification
:opened="cooldownToastOpened"
position="center"
vertical-position="bottom"
:text="phone.t('Apps.weather.errors.reload_cooldown')"
@click="closeCooldownToast"
>
{{ phone.t('Apps.weather.errors.reload_cooldown') }}
</SkyToast>
/>
</SkyAppPage>
</template>
+4 -6
View File
@@ -19,7 +19,7 @@ import {
SkySearchbar,
SkySegmented,
SkySegmentedButton,
SkyToast,
SkyNotification,
} from '@/ui'
import {
BriefcaseBusiness,
@@ -1514,13 +1514,11 @@ onBeforeUnmount(() => {
</template>
</sky-dialog>
<sky-toast
<sky-notification
:opened="toastOpened"
position="center"
:text="toastText"
@click="toastOpened = false"
>
{{ toastText }}
</sky-toast>
/>
</sky-app-page>
</template>