mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-09-04 17:23:25 +00:00
feat(feather): complete SkyUI interaction polish
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const source = readFileSync(
|
||||
new URL('./FeatherApp.vue', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
const postCard = readFileSync(
|
||||
new URL('../../components/feather/FeatherPostCard.vue', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
|
||||
describe('FeatherApp Sky UI contract', () => {
|
||||
it('uses explicit Sky UI components without Konsta-style aliases', () => {
|
||||
expect(source).not.toContain("from 'konsta/vue'")
|
||||
expect(postCard).not.toContain("from 'konsta/vue'")
|
||||
expect(postCard).toContain('import { SkyButton, SkyGlass, SkyIcon }')
|
||||
expect(postCard).not.toMatch(/Sky[A-Za-z]+ as k[A-Za-z]+/)
|
||||
expect(postCard).not.toMatch(/<\/?k[A-Z]/)
|
||||
})
|
||||
|
||||
it('gives likes and bookmarks a reduced-motion-safe pulse animation', () => {
|
||||
expect(postCard).toContain(
|
||||
"const reactionPulse = ref<'like' | 'bookmark' | null>(null)",
|
||||
)
|
||||
expect(postCard).toContain('@click.stop="react(\'like\')"')
|
||||
expect(postCard).toContain('@click.stop="react(\'bookmark\')"')
|
||||
expect(postCard).toContain("'is-pulsing': reactionPulse === 'like'")
|
||||
expect(postCard).toContain("'is-pulsing': reactionPulse === 'bookmark'")
|
||||
expect(postCard).toContain('@keyframes feather-reaction-pop')
|
||||
expect(postCard).toContain('@keyframes feather-reaction-ring')
|
||||
expect(postCard).toContain('@media (prefers-reduced-motion: reduce)')
|
||||
})
|
||||
|
||||
it('keeps the post header actions visible and compact', () => {
|
||||
expect(postCard).toContain(
|
||||
':aria-label="phone.t(\'Apps.feather.moreActions\')"',
|
||||
)
|
||||
expect(postCard).toMatch(
|
||||
/<SkyButton\s+icon-only\s+rounded\s+small\s+tonal\s+class="feather-more"/s,
|
||||
)
|
||||
expect(postCard).toMatch(
|
||||
/\.feather-follow\s*\{[^}]*--sky-app-accent:\s*var\(--feather-blue,[^}]*color:\s*var\(--feather-blue,/s,
|
||||
)
|
||||
expect(postCard).not.toMatch(
|
||||
/\.feather-follow\s*\{[^}]*--sky-app-accent:\s*transparent/s,
|
||||
)
|
||||
expect(postCard).toMatch(
|
||||
/\.feather-more\s*\{[^}]*width:\s*27px[^}]*height:\s*27px[^}]*border:\s*1px/s,
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps the composer below the iPhone header with compact actions', () => {
|
||||
expect(source).toContain("'feather-app--composer':")
|
||||
expect(source).toMatch(
|
||||
/\.feather-app--active\.feather-app--composer \.feather-navbar\s*\{[^}]*--sky-safe-area-top:\s*54px/s,
|
||||
)
|
||||
expect(source).toMatch(
|
||||
/\.feather-app--active\.feather-app--composer > \.feather-composer\s*\{[^}]*top:\s*112px[^}]*padding-top:\s*18px/s,
|
||||
)
|
||||
expect(source).toMatch(
|
||||
/\.feather-composer-close\s*\{[^}]*width:\s*32px[^}]*height:\s*32px/s,
|
||||
)
|
||||
expect(source).toMatch(
|
||||
/\.feather-composer-publish\s*\{[^}]*min-width:\s*52px[^}]*min-height:\s*28px/s,
|
||||
)
|
||||
})
|
||||
|
||||
it('renders a white profile banner title and owner logout icon at the top right', () => {
|
||||
const profileStart = source.indexOf('class="feather-profile"')
|
||||
const tabsStart = source.indexOf(
|
||||
'class="feather-profile-tabs"',
|
||||
profileStart,
|
||||
)
|
||||
const profile = source.slice(profileStart, tabsStart)
|
||||
|
||||
expect(profile).toContain('class="feather-profile__cover-title"')
|
||||
expect(profile).toContain('class="feather-profile__logout"')
|
||||
expect(profile).toContain('v-if="activeProfile.is_owner"')
|
||||
expect(profile).toContain(':aria-label="phone.t(\'Common.signOut\')"')
|
||||
expect(profile).toContain('@click="logoutDialogOpen = true"')
|
||||
expect(profile).not.toContain('feather-profile-action--logout')
|
||||
expect(source).toMatch(
|
||||
/\.feather-profile__cover-title\s*\{[^}]*color:\s*#fff/s,
|
||||
)
|
||||
expect(source).toMatch(
|
||||
/\.feather-app--active \.feather-profile__logout\s*\{[^}]*top:\s*10px[^}]*right:\s*10px/s,
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -847,6 +847,8 @@ onMounted(async () => {
|
||||
screen === 'main' &&
|
||||
tab === 'home',
|
||||
'feather-app--light': !phone.isDarkMode,
|
||||
'feather-app--composer':
|
||||
feather.onboarded && isAuthenticated && screen === 'composer',
|
||||
'feather-app--section':
|
||||
feather.onboarded &&
|
||||
isAuthenticated &&
|
||||
@@ -892,7 +894,7 @@ onMounted(async () => {
|
||||
:aria-label="t('cancel')"
|
||||
@click="goBack"
|
||||
>
|
||||
<X :size="19" />
|
||||
<X :size="16" />
|
||||
</SkyLink>
|
||||
<SkyNavbarBackLink
|
||||
v-else
|
||||
@@ -1386,7 +1388,23 @@ onMounted(async () => {
|
||||
>
|
||||
<SkyGlass :highlight="false" class="feather-profile-glass">
|
||||
<div class="feather-profile">
|
||||
<div class="feather-profile__cover"></div>
|
||||
<div class="feather-profile__cover">
|
||||
<span class="feather-profile__cover-title">
|
||||
<Feather :size="17" :stroke-width="2" />
|
||||
{{ activeProfile.display_name }}
|
||||
</span>
|
||||
<SkyButton
|
||||
v-if="activeProfile.is_owner"
|
||||
icon-only
|
||||
rounded
|
||||
tonal
|
||||
class="feather-profile__logout"
|
||||
:aria-label="phone.t('Common.signOut')"
|
||||
@click="logoutDialogOpen = true"
|
||||
>
|
||||
<LogOut :size="16" />
|
||||
</SkyButton>
|
||||
</div>
|
||||
<div class="feather-profile__top">
|
||||
<div class="feather-profile__avatar">
|
||||
<img
|
||||
@@ -1462,15 +1480,6 @@ onMounted(async () => {
|
||||
<PencilLine :size="15" />
|
||||
<span>{{ t('editProfile') }}</span>
|
||||
</SkyButton>
|
||||
<SkyButton
|
||||
rounded
|
||||
outline
|
||||
class="feather-profile-action feather-profile-action--logout"
|
||||
@click="logoutDialogOpen = true"
|
||||
>
|
||||
<LogOut :size="15" />
|
||||
<span>{{ phone.t('Common.signOut') }}</span>
|
||||
</SkyButton>
|
||||
</div>
|
||||
</div>
|
||||
</SkyGlass>
|
||||
@@ -3723,8 +3732,13 @@ onMounted(async () => {
|
||||
color: var(--feather-muted);
|
||||
}
|
||||
.feather-app--active :deep(.feather-follow) {
|
||||
--sky-button-bg-color: transparent;
|
||||
--sky-button-text-color: var(--feather-blue);
|
||||
--sky-app-accent: var(--feather-blue);
|
||||
--sky-app-accent-soft: color-mix(
|
||||
in srgb,
|
||||
var(--feather-blue) 14%,
|
||||
transparent
|
||||
);
|
||||
color: var(--feather-blue);
|
||||
}
|
||||
.feather-app--active .feather-trend,
|
||||
.feather-app--active .feather-person,
|
||||
@@ -4260,9 +4274,51 @@ onMounted(async () => {
|
||||
background: transparent;
|
||||
}
|
||||
.feather-app--active .feather-profile__cover {
|
||||
position: relative;
|
||||
height: 112px;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #173f6d, #58a6ff);
|
||||
}
|
||||
.feather-profile__cover-title {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
max-width: calc(100% - 72px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
line-height: 20px;
|
||||
text-overflow: ellipsis;
|
||||
text-shadow: 0 2px 8px rgb(0 0 0 / 34%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.feather-profile__cover-title svg {
|
||||
flex: none;
|
||||
}
|
||||
.feather-app--active .feather-profile__logout {
|
||||
--sky-button-bg-color: rgb(8 22 39 / 38%);
|
||||
--sky-button-text-color: #fff;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 34px;
|
||||
min-width: 34px;
|
||||
height: 34px;
|
||||
min-height: 34px;
|
||||
border: 1px solid rgb(255 255 255 / 24%);
|
||||
padding: 0;
|
||||
color: #fff;
|
||||
background: rgb(8 22 39 / 38%);
|
||||
box-shadow: inset 0 1px 0 rgb(255 255 255 / 16%);
|
||||
backdrop-filter: blur(14px) saturate(150%);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(150%);
|
||||
}
|
||||
.feather-app--active .feather-profile__top {
|
||||
height: 50px;
|
||||
padding: 0 13px;
|
||||
@@ -4336,13 +4392,6 @@ onMounted(async () => {
|
||||
font-size: 10.5px;
|
||||
font-weight: 750;
|
||||
}
|
||||
.feather-app--active
|
||||
.feather-profile__actions
|
||||
:deep(.feather-profile-action--logout) {
|
||||
grid-column: 1 / -1;
|
||||
border-color: color-mix(in srgb, #f04f65 62%, transparent);
|
||||
color: #f04f65;
|
||||
}
|
||||
.feather-onboarding__logout {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
@@ -4465,10 +4514,17 @@ onMounted(async () => {
|
||||
padding: 13px 13px 34px;
|
||||
background: transparent;
|
||||
}
|
||||
.feather-app--active.feather-app--composer .feather-navbar {
|
||||
--sky-safe-area-top: 54px;
|
||||
}
|
||||
.feather-app--active.feather-app--composer > .feather-composer {
|
||||
top: 112px;
|
||||
padding-top: 18px;
|
||||
}
|
||||
.feather-composer-close {
|
||||
display: grid;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
place-items: center;
|
||||
border: 1px solid var(--feather-border);
|
||||
border-radius: 50%;
|
||||
@@ -4476,7 +4532,13 @@ onMounted(async () => {
|
||||
color: inherit;
|
||||
background: var(--feather-panel);
|
||||
}
|
||||
.feather-composer-publish,
|
||||
.feather-composer-publish {
|
||||
min-width: 52px;
|
||||
min-height: 28px;
|
||||
padding-inline: 10px;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.feather-edit__navbar-save {
|
||||
min-width: 58px;
|
||||
min-height: 30px;
|
||||
|
||||
Reference in New Issue
Block a user