feat(messages): overhaul messaging and contact flows

This commit is contained in:
Dominik
2026-08-16 23:37:40 +02:00
parent 49bd7f358f
commit 1c085f975f
36 changed files with 1937 additions and 478 deletions
+4 -3
View File
@@ -23,6 +23,9 @@ describe('SkyDropdown', () => {
it('supports checked, submenu, destructive, disabled, and divided items', () => {
expect(component).toContain("'menuitemradio'")
expect(component).toContain(':aria-checked=')
expect(component).toContain("section.group ? 'group' : 'presentation'")
expect(component).toContain('section.group ? section.label : undefined')
expect(component).toContain('groupLabel?: string')
expect(component).toContain(':aria-haspopup=')
expect(component).toContain('item.destructive')
expect(component).toContain('item.disabled')
@@ -35,9 +38,7 @@ describe('SkyDropdown', () => {
expect(overlays).toMatch(
/\.sky-dropdown__item\s*\{[^}]*min-height:\s*var\(--sky-touch-target, 44px\)/s,
)
expect(overlays).toMatch(
/\.sky-dropdown__menu\s*\{[^}]*padding:\s*6px;/s,
)
expect(overlays).toMatch(/\.sky-dropdown__menu\s*\{[^}]*padding:\s*6px;/s)
expect(overlays).toMatch(
/\.sky-dropdown__item\s*\{[^}]*border-radius:\s*12px;/s,
)
+64 -27
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { Check, ChevronRight } from 'lucide-vue-next'
import { computed } from 'vue'
import SkyPopover from './SkyPopover.vue'
@@ -9,13 +10,15 @@ interface SkyDropdownItem {
checked?: boolean
destructive?: boolean
disabled?: boolean
group?: string
groupLabel?: string
id: string
label: string
separatorBefore?: boolean
submenu?: boolean
}
withDefaults(
const props = withDefaults(
defineProps<{
items: readonly SkyDropdownItem[]
label: string
@@ -34,6 +37,33 @@ const emit = defineEmits<{
positionerror: [reason: string]
select: [id: string, event: MouseEvent]
}>()
const menuSections = computed(() => {
const sections: Array<{
group: string | null
items: SkyDropdownItem[]
key: string
label?: string
}> = []
props.items.forEach((item) => {
const group = item.group ?? null
const previous = sections[sections.length - 1]
if (!previous || previous.group !== group) {
sections.push({
group,
items: [item],
key: `${group ?? 'items'}:${sections.length}`,
label: item.groupLabel,
})
return
}
previous.items.push(item)
})
return sections
})
</script>
<template>
@@ -50,33 +80,40 @@ const emit = defineEmits<{
@positionerror="emit('positionerror', $event)"
>
<div class="sky-dropdown__menu">
<button
v-for="item in items"
:key="item.id"
class="sky-dropdown__item"
:class="{
'sky-dropdown__item--destructive': item.destructive,
'sky-dropdown__item--separator': item.separatorBefore,
}"
:aria-checked="item.checked === undefined ? undefined : item.checked"
:aria-haspopup="item.submenu ? 'menu' : undefined"
:disabled="item.disabled"
:role="item.checked === undefined ? 'menuitem' : 'menuitemradio'"
type="button"
@click="emit('select', item.id, $event)"
<div
v-for="section in menuSections"
:key="section.key"
:aria-label="section.group ? section.label : undefined"
:role="section.group ? 'group' : 'presentation'"
>
<span class="sky-dropdown__indicator" aria-hidden="true">
<Check v-if="item.checked" :size="21" :stroke-width="2.5" />
</span>
<span class="sky-dropdown__label">{{ item.label }}</span>
<ChevronRight
v-if="item.submenu"
class="sky-dropdown__chevron"
:size="21"
:stroke-width="2.5"
aria-hidden="true"
/>
</button>
<button
v-for="item in section.items"
:key="item.id"
class="sky-dropdown__item"
:class="{
'sky-dropdown__item--destructive': item.destructive,
'sky-dropdown__item--separator': item.separatorBefore,
}"
:aria-checked="item.checked === undefined ? undefined : item.checked"
:aria-haspopup="item.submenu ? 'menu' : undefined"
:disabled="item.disabled"
:role="item.checked === undefined ? 'menuitem' : 'menuitemradio'"
type="button"
@click="emit('select', item.id, $event)"
>
<span class="sky-dropdown__indicator" aria-hidden="true">
<Check v-if="item.checked" :size="21" :stroke-width="2.5" />
</span>
<span class="sky-dropdown__label">{{ item.label }}</span>
<ChevronRight
v-if="item.submenu"
class="sky-dropdown__chevron"
:size="21"
:stroke-width="2.5"
aria-hidden="true"
/>
</button>
</div>
</div>
</SkyPopover>
</template>