mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-09-04 09:13:24 +00:00
ENH - polish core phone app interfaces
Adds an active-call Dynamic Island and refines the Phone, Health, Notes, and Companies experiences with shared Sky UI behavior, improved keyboard interaction, and localized note deletion confirmation. Updates the relevant contract and component tests.
This commit is contained in:
@@ -21,6 +21,8 @@ describe('NotesRichTextEditor formatting tabbar', () => {
|
||||
expect(source).toContain('@click="formatMode = true"')
|
||||
expect(source).toContain('@click="formatMode = false"')
|
||||
expect(source).toContain(':disabled="editor.state.selection.empty"')
|
||||
expect(source).toContain('class="notes-rich-editor sky-ui-provider"')
|
||||
expect(source).toContain("'sky-ui-provider--dark': dark")
|
||||
expect(source).not.toContain('notes-rich-editor__toolbar-row')
|
||||
expect(source).not.toContain('scrollToolbar')
|
||||
})
|
||||
|
||||
@@ -263,8 +263,11 @@ onBeforeUnmount(() => editor.value?.destroy())
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="notes-rich-editor"
|
||||
:class="{ 'notes-rich-editor--dark': dark }"
|
||||
class="notes-rich-editor sky-ui-provider"
|
||||
:class="{
|
||||
'notes-rich-editor--dark': dark,
|
||||
'sky-ui-provider--dark': dark,
|
||||
}"
|
||||
>
|
||||
<EditorContent
|
||||
v-if="editor"
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const component = readFileSync(
|
||||
new URL('./PhoneDynamicIsland.vue', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
const app = readFileSync(new URL('../App.vue', import.meta.url), 'utf8')
|
||||
|
||||
describe('PhoneDynamicIsland contract', () => {
|
||||
it('renders only a ringing incoming call with Sky actions', () => {
|
||||
expect(component).toContain("call?.direction === 'incoming'")
|
||||
expect(component).toContain("call.state === 'ringing'")
|
||||
expect(component).toContain('<SkyButton')
|
||||
expect(component).toContain("await calls.answer()")
|
||||
expect(component).toContain("await calls.decline()")
|
||||
})
|
||||
|
||||
it('keeps incoming calls in the current app until accepted', () => {
|
||||
expect(app).toContain("call.state !== 'ringing'")
|
||||
expect(app).toContain("call.direction !== 'incoming'")
|
||||
expect(app).toContain('<PhoneDynamicIsland @accepted="openAcceptedCall" />')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,81 @@
|
||||
<script setup lang="ts">
|
||||
import { Phone, PhoneOff } from 'lucide-vue-next'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useCallsStore } from '@/stores/calls'
|
||||
import { usePhoneStore } from '@/stores/phone'
|
||||
import { SkyButton } from '@/ui'
|
||||
|
||||
const emit = defineEmits<{ accepted: [] }>()
|
||||
const calls = useCallsStore()
|
||||
const phone = usePhoneStore()
|
||||
const pendingAction = ref<'answer' | 'decline' | null>(null)
|
||||
|
||||
const incomingCall = computed(() => {
|
||||
const call = calls.activeCall
|
||||
return call?.direction === 'incoming' && call.state === 'ringing'
|
||||
? call
|
||||
: null
|
||||
})
|
||||
|
||||
const callerLabel = computed(() => {
|
||||
const number = incomingCall.value?.otherNumber ?? ''
|
||||
return (
|
||||
calls.contacts.find((contact) => contact.phone_number === number)?.name ||
|
||||
number
|
||||
)
|
||||
})
|
||||
|
||||
async function answer(): Promise<void> {
|
||||
if (pendingAction.value || !incomingCall.value) return
|
||||
pendingAction.value = 'answer'
|
||||
const response = await calls.answer()
|
||||
pendingAction.value = null
|
||||
if (response.success) emit('accepted')
|
||||
}
|
||||
|
||||
async function decline(): Promise<void> {
|
||||
if (pendingAction.value || !incomingCall.value) return
|
||||
pendingAction.value = 'decline'
|
||||
await calls.decline()
|
||||
pendingAction.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="phone-dynamic-island">
|
||||
<aside
|
||||
v-if="incomingCall"
|
||||
class="phone-dynamic-island sky-ui-provider sky-ui-provider--dark"
|
||||
:aria-label="phone.t('Apps.phone.incoming')"
|
||||
role="region"
|
||||
>
|
||||
<div class="phone-dynamic-island__caller">
|
||||
<span>{{ phone.t('Apps.phone.incoming') }}</span>
|
||||
<strong>{{ callerLabel }}</strong>
|
||||
</div>
|
||||
<div class="phone-dynamic-island__actions">
|
||||
<SkyButton
|
||||
:aria-label="phone.t('Apps.phone.decline')"
|
||||
:disabled="pendingAction !== null"
|
||||
icon-only
|
||||
rounded
|
||||
variant="danger"
|
||||
@click="decline"
|
||||
>
|
||||
<PhoneOff aria-hidden="true" />
|
||||
</SkyButton>
|
||||
<SkyButton
|
||||
:aria-label="phone.t('Apps.phone.answer')"
|
||||
:disabled="pendingAction !== null"
|
||||
icon-only
|
||||
rounded
|
||||
class="phone-dynamic-island__answer"
|
||||
@click="answer"
|
||||
>
|
||||
<Phone aria-hidden="true" />
|
||||
</SkyButton>
|
||||
</div>
|
||||
</aside>
|
||||
</Transition>
|
||||
</template>
|
||||
Reference in New Issue
Block a user