feat(app): mobile improvements — native-feel viewport, keyboard, HIG tabs, PWA fix
- Lock viewport: position:fixed on html/body prevents iOS bounce scroll - No zoom on input focus: maximum-scale=1, user-scalable=no - Keyboard-responsive chat: visualViewport API detects keyboard, hides tab bar, scrolls chat to bottom, scrollIntoView on input focus - iOS HIG tab bar: 49pt height, vertical icon+label, safe-area-inset-bottom - PWA fix: manifest start_url/scope/id changed from '/' to './' for subpath deployment compatibility - PlayerBar: variant prop (fixed/inline), inline on mobile above tab bar, fixed on desktop. No more overlap with tab bar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
799ca131d9
commit
a0a9cf8e54
@@ -106,7 +106,7 @@
|
||||
@keydown="handleKeydown"
|
||||
@input="autoResize"
|
||||
@paste="onPaste"
|
||||
@focus="focused = true"
|
||||
@focus="onInputFocus"
|
||||
@blur="focused = false"
|
||||
/>
|
||||
<button
|
||||
@@ -252,6 +252,13 @@ function closePalette() {
|
||||
}
|
||||
}
|
||||
|
||||
function onInputFocus() {
|
||||
focused.value = true
|
||||
nextTick(() => {
|
||||
textareaRef.value?.scrollIntoView({ block: 'end', behavior: 'smooth' })
|
||||
})
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (isPaletteMode.value && !paletteRef.value?.hasSelectedTemplate) {
|
||||
if (e.key === 'ArrowUp') {
|
||||
|
||||
@@ -179,6 +179,7 @@ const chatStore = useChatStore()
|
||||
const { sendMessage, stopGeneration, editAndResend, regenerateLastResponse, activeModel } = useAI()
|
||||
const { updatePanelFromText, panelOpen, activeTab, availableTabs, setActiveTab, enterDesignSystemMode } = useContentPanel()
|
||||
import { useCodeContext } from '@/composables/useCodeContext'
|
||||
import { useVisualViewport } from '@/composables/useVisualViewport'
|
||||
const codeContext = useCodeContext()
|
||||
import { usePersonaStore } from '@/stores/personas'
|
||||
const personaStore = usePersonaStore()
|
||||
@@ -434,6 +435,12 @@ watch(chatCollapsed, (collapsed, wasCollapsed) => {
|
||||
}
|
||||
})
|
||||
|
||||
// Scroll to bottom when mobile keyboard opens so latest messages + input stay visible
|
||||
const { isKeyboardOpen } = useVisualViewport()
|
||||
watch(isKeyboardOpen, (open) => {
|
||||
if (open) scrollToBottom()
|
||||
})
|
||||
|
||||
// Save/restore scroll position on conversation switch
|
||||
watch(
|
||||
() => chatStore.activeConversationId,
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
<Transition name="player-slide">
|
||||
<div
|
||||
v-if="hasTrack"
|
||||
class="fixed bottom-0 left-0 right-0 z-[999] flex items-center gap-4 px-4 py-3 path-glass-card rounded-none border-t-0 border-x-0 shadow-2xl"
|
||||
:class="[
|
||||
props.variant === 'fixed'
|
||||
? 'fixed bottom-0 left-0 right-0 z-[999]'
|
||||
: 'w-full shrink-0',
|
||||
'flex items-center gap-4 px-4 py-3 path-glass-card rounded-none border-t-0 border-x-0 shadow-2xl'
|
||||
]"
|
||||
>
|
||||
<!-- Plyr container: YouTube requires min 200x200px. Kept off-screen but sized. -->
|
||||
<div
|
||||
@@ -113,6 +118,10 @@ import { ref, computed, onMounted, watch, nextTick } from 'vue'
|
||||
import { usePlayer } from '@/composables/usePlayer'
|
||||
import { fetchMusicCover } from '@/composables/useImageFallback'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
variant?: 'fixed' | 'inline'
|
||||
}>(), { variant: 'fixed' })
|
||||
|
||||
const {
|
||||
currentSong,
|
||||
hasTrack,
|
||||
|
||||
Reference in New Issue
Block a user