From 49bd7f358f2e30cbc0dfa9437ef6606d5075f70c Mon Sep 17 00:00:00 2001 From: "Leon.Schmidt" Date: Sun, 16 Aug 2026 23:32:29 +0200 Subject: [PATCH] ENH - unify phone notifications as popups Replace the central Konsta notification banner with the shared Sky UI popup across phone and preview surfaces. Add light and dark materials, app icons, timestamps, routed opening, swipe dismissal, and contract coverage. --- frontend/src/App.vue | 11 ++ frontend/src/assets/main.css | 49 +++++- .../components/NotificationPhonePreview.vue | 1 + .../PhoneNotifications.contract.test.ts | 63 +++++++ .../src/components/PhoneNotifications.vue | 154 +++++++++++++++--- frontend/src/ui/overlays.css | 40 +++-- .../src/ui/overlays/SkyNotification.test.ts | 26 +-- 7 files changed, 292 insertions(+), 52 deletions(-) create mode 100644 frontend/src/components/PhoneNotifications.contract.test.ts diff --git a/frontend/src/App.vue b/frontend/src/App.vue index c980f76..81ab047 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1311,6 +1311,17 @@ onMounted(() => { if (developmentParameters.has('payphonePreview')) { openDevelopmentPayphonePreview() } + if (developmentParameters.has('notificationPreview')) { + window.setTimeout(() => { + notifications.show({ + appId: 'messages', + persistent: true, + route: '/apps/messages', + text: 'You still got that spare alternator?', + title: 'Tommy V', + }) + }, 250) + } } }) diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index 4e2c2cf..50d3768 100644 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -536,19 +536,62 @@ button { border: 0 !important; opacity: 0 !important; } +.phone-notification-provider { + --phone-notification-background: rgb(247 247 248 / 94%); + --phone-notification-shadow: + 0 10px 30px rgb(0 0 0 / 18%), inset 0 0.5px 0 rgb(255 255 255 / 90%); + position: absolute; + z-index: 85; + inset: 0; + pointer-events: none; +} +.phone-notification-provider.sky-ui-provider--dark { + --phone-notification-background: rgb(36 36 39 / 94%); + --phone-notification-shadow: + 0 12px 34px rgb(0 0 0 / 36%), inset 0 0.5px 0 rgb(255 255 255 / 16%); +} .phone-notification { + --phone-notification-drag-y: 0px; z-index: 85 !important; - top: 68px !important; - right: 8px !important; - left: 8px !important; + top: 51px !important; + right: 10px !important; + left: 10px !important; width: auto !important; margin-left: 0 !important; + pointer-events: auto; + background: var(--phone-notification-background) !important; + box-shadow: var(--phone-notification-shadow) !important; + transform: translate3d(0, var(--phone-notification-drag-y), 0); + transition: transform 280ms var(--sky-ease-out, ease-out); + will-change: transform; } .phone-notification__icon { width: 38px; height: 38px; + border-radius: 10px; object-fit: contain; } +.phone-notification__open { + position: absolute; + z-index: 2; + inset: 0; + padding: 0; + border: 0; + border-radius: inherit; + background: transparent; + cursor: pointer; + -webkit-tap-highlight-color: transparent; +} +.phone-notification__open:active { + background: var(--sky-pressed); +} +.phone-notification__open:focus-visible { + outline: 2px solid var(--sky-app-accent); + outline-offset: -3px; +} +.phone-notification.phone-notification--dragging { + transition: none !important; +} .phone-status-bar { position: absolute; z-index: 97; diff --git a/frontend/src/components/NotificationPhonePreview.vue b/frontend/src/components/NotificationPhonePreview.vue index cccefab..8585f6a 100644 --- a/frontend/src/components/NotificationPhonePreview.vue +++ b/frontend/src/components/NotificationPhonePreview.vue @@ -73,6 +73,7 @@ const wrapperStyle = computed(() => ({ preview /> { + it('uses the shared Sky popup for every central phone notification', () => { + expect(source).not.toContain("from 'konsta/vue'") + expect(source).toContain( + "import { SkyNotification, SkyProvider } from '@/ui'", + ) + expect(source).toContain(' { + expect(source).toContain('getPhoneApp(props.notification.appId)?.iconImage') + expect(source).toContain('class="phone-notification__icon"') + expect(source).toContain('class="phone-notification__open"') + expect(source).toContain(':aria-label="phone.t(\'Notifications.open\')"') + expect(source).toContain('@keydown.esc.stop="emit(\'close\')"') + }) + + it('supports an upward dismiss gesture without a visible close button', () => { + expect(source).toContain('@pointerdown="beginDismissGesture"') + expect(source).toContain('@pointermove="moveDismissGesture"') + expect(source).toContain('pointerOffset <= -28') + expect(source).toContain('velocityY <= -0.3') + expect(source).not.toContain('button="close"') + expect(source).not.toContain('