fix(feather): refine more menu and profile actions

This commit is contained in:
Leon.Schmidt
2026-08-18 14:00:16 +02:00
parent 481f7e5be7
commit 1b191fd127
2 changed files with 132 additions and 64 deletions
@@ -75,20 +75,27 @@ describe('FeatherApp Sky UI contract', () => {
/v-if="screen === 'composer'"[\s\S]*?variant="secondary"[\s\S]*?class="feather-composer-publish"/,
)
expect(source).toMatch(
/v-else-if="screen === 'edit'"[\s\S]*?variant="secondary"[\s\S]*?class="feather-edit__navbar-save"/,
/v-else-if="screen === 'edit'"[\s\S]*?variant="plain"[\s\S]*?class="feather-edit__navbar-save"/,
)
})
it('uses the shared phone action sheet for every post more menu', () => {
expect(source).toContain('<SkyActionSheet')
expect(source).toContain('<SkyActionGroup')
expect(source).toContain('<SkyActionButton')
expect(source).toContain('<SkyActionsLabel')
it('uses the shared draggable phone sheet for every post more menu', () => {
expect(source).toContain('<SkySheet')
expect(source).toContain('function openPostMenu(post: FeatherPost)')
expect(source).toContain('@menu="openPostMenu"')
expect(source).toContain('@click="openPostMenu(post)"')
expect(source).toContain('@backdropclick="closePostMenu"')
expect(source).not.toContain('<SkySheet :opened="menuPost !== null"')
expect(source).toMatch(
/<div class="feather-post-menu">\s*<SkySheet[\s\S]*?swipe-to-close/,
)
expect(source).toContain('swipe-to-close')
expect(source).toContain('@swipeclose="closePostMenu"')
expect(source).toContain('@grabberclick="closePostMenu"')
expect(source).not.toContain('.feather-post-menu :deep(.sky-sheet__panel)')
expect(source).toMatch(
/\.feather-post-menu__action\s*\{[^}]*min-height:\s*46px[^}]*font-size:\s*12px/s,
)
expect(source).not.toContain('<SkyActionSheet')
})
it('renders compact Instagram-style comments with a bottom message bar', () => {
@@ -153,7 +160,11 @@ describe('FeatherApp Sky UI contract', () => {
/v-else-if="screen === 'edit'"[\s\S]*?icon-only[\s\S]*?class="feather-edit__navbar-save"[\s\S]*?<SkySpinner v-if="busy"[\s\S]*?<Check v-else/,
)
expect(source).toMatch(
/\.feather-edit__navbar-save\s*\{[^}]*width:\s*32px[^}]*height:\s*32px[^}]*color:\s*#fff/s,
/\.feather-edit__navbar-save\s*\{[^}]*width:\s*44px !important[^}]*height:\s*44px !important[^}]*border-radius:\s*50% !important[^}]*background:\s*transparent !important[^}]*box-shadow:\s*none/s,
)
expect(source).toContain("'feather-edit__navbar-back': screen === 'edit'")
expect(source).toMatch(
/\.feather-edit__navbar-back :deep\(\.sky-navbar-back-link__icon\)\s*\{[^}]*translateX\(2px\)/s,
)
expect(source).not.toContain("profileView === 'media'")
expect(source).not.toContain("selectProfileView('media')")
+113 -56
View File
@@ -33,10 +33,6 @@ import {
X,
} from 'lucide-vue-next'
import {
SkyActionButton,
SkyActionGroup,
SkyActionSheet,
SkyActionsLabel,
SkyBlock,
SkyButton,
SkyFab,
@@ -984,6 +980,7 @@ onMounted(async () => {
</SkyLink>
<SkyNavbarBackLink
v-else
:class="{ 'feather-edit__navbar-back': screen === 'edit' }"
:text="t('back')"
:show-text="false"
@click="goBack"
@@ -1005,8 +1002,7 @@ onMounted(async () => {
v-else-if="screen === 'edit'"
icon-only
rounded
small
variant="secondary"
variant="plain"
:disabled="!canSaveProfile"
class="feather-edit__navbar-save"
:aria-label="busy ? t('loading') : t('saveProfile')"
@@ -2220,44 +2216,60 @@ onMounted(async () => {
</SkyBlock>
</SkySheet>
<SkyActionSheet
:opened="menuPost !== null && !reportOpen"
:label="t('moreActions')"
@backdropclick="closePostMenu"
@escape="closePostMenu"
>
<SkyActionGroup class="feather-post-actions">
<SkyActionsLabel v-if="menuPost">
@{{ menuPost.handle }}
</SkyActionsLabel>
<SkyActionButton
v-if="menuPost?.is_owner"
class="feather-post-actions__danger"
@click="deletePost"
>
<Trash2 :size="18" />
{{ t('delete') }}
</SkyActionButton>
<template v-else>
<SkyActionButton @click="openPostReport">
<Flag :size="18" />
<span>{{ t('report') }}</span>
</SkyActionButton>
<SkyActionButton
class="feather-post-actions__danger"
@click="blockPostAuthor"
>
<Ban :size="18" />
<span>{{ t('block', { handle: menuPost?.handle ?? '' }) }}</span>
</SkyActionButton>
</template>
</SkyActionGroup>
<SkyActionGroup>
<SkyActionButton bold @click="closePostMenu">
{{ t('cancel') }}
</SkyActionButton>
</SkyActionGroup>
</SkyActionSheet>
<div class="feather-post-menu">
<SkySheet
:opened="menuPost !== null && !reportOpen"
:aria-label="t('moreActions')"
swipe-to-close
grabber-clickable
:grabber-label="t('cancel')"
@backdropclick="closePostMenu"
@escape="closePostMenu"
@grabberclick="closePostMenu"
@swipeclose="closePostMenu"
>
<section v-if="menuPost" class="feather-post-menu__content">
<header class="feather-post-menu__header" data-sky-sheet-drag-handle>
<span>@{{ menuPost.handle }}</span>
<h2>{{ t('moreActions') }}</h2>
</header>
<div class="feather-post-menu__group">
<SkyButton
v-if="menuPost?.is_owner"
block
clear
class="feather-post-menu__action feather-post-menu__action--danger"
@click="deletePost"
>
<Trash2 :size="18" />
<span>{{ t('delete') }}</span>
</SkyButton>
<template v-else>
<SkyButton
block
clear
class="feather-post-menu__action"
@click="openPostReport"
>
<Flag :size="18" />
<span>{{ t('report') }}</span>
</SkyButton>
<SkyButton
block
clear
class="feather-post-menu__action feather-post-menu__action--danger"
@click="blockPostAuthor"
>
<Ban :size="18" />
<span>{{
t('block', { handle: menuPost?.handle ?? '' })
}}</span>
</SkyButton>
</template>
</div>
</section>
</SkySheet>
</div>
<SkySheet :opened="reportOpen" @backdropclick="closePostReport">
<SkyBlock strong inset class="feather-report">
@@ -4799,15 +4811,25 @@ onMounted(async () => {
font-weight: 800;
}
.feather-edit__navbar-save {
--sky-surface-muted: #2c3035;
width: 32px;
min-width: 32px;
height: 32px;
min-height: 32px;
border-color: rgb(255 255 255 / 14%);
display: grid;
width: 44px !important;
min-width: 44px;
height: 44px !important;
min-height: 44px;
flex: 0 0 44px;
place-items: center;
border-color: transparent;
border-radius: 50% !important;
padding: 0;
background: transparent !important;
color: #fff;
box-shadow: 0 5px 14px rgb(0 0 0 / 18%);
box-shadow: none;
}
.feather-edit__navbar-save :deep(svg) {
display: block;
}
.feather-edit__navbar-back :deep(.sky-navbar-back-link__icon) {
transform: translateX(2px);
}
.feather-composer-card {
flex: none;
@@ -5252,13 +5274,48 @@ onMounted(async () => {
color: inherit;
background: var(--feather-panel);
}
.feather-post-actions__danger {
.feather-post-menu__content {
padding: 0 14px calc(var(--sky-safe-area-bottom) + 14px);
}
.feather-post-menu__header {
padding: 0 2px 11px;
touch-action: none;
}
.feather-post-menu__header span {
display: block;
color: var(--feather-muted);
font-size: 10px;
font-weight: 700;
}
.feather-post-menu__header h2 {
margin: 2px 0 0;
font-size: 17px;
letter-spacing: -0.25px;
}
.feather-post-menu__group {
overflow: hidden;
border: 1px solid var(--feather-border);
border-radius: 16px;
background: color-mix(in srgb, currentColor 5%, transparent);
}
.feather-post-menu__action {
min-height: 46px;
justify-content: flex-start;
gap: 9px;
border-radius: 0;
padding: 9px 13px;
color: inherit;
font-size: 12px;
font-weight: 700;
line-height: 18px;
}
.feather-post-menu__action + .feather-post-menu__action {
border-top: 1px solid var(--feather-border);
}
.feather-post-menu__action--danger {
color: var(--sky-danger);
}
.feather-post-actions :deep(.sky-action-button) {
gap: 8px;
}
.feather-post-actions__danger svg {
.feather-post-menu__action svg {
flex: none;
}
@supports not (color: color-mix(in srgb, white, black)) {