mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
The browser H5 surface now switches to a phone-oriented shell: the sidebar becomes a closed-by-default drawer, chat stays primary, workspace and terminal panels stay off the mobile chat surface, composer controls use larger touch targets, and mobile menus avoid desktop-only widths and keyboard hints. Desktop and Tauri sidebar behavior remain on the existing store-driven path. Constraint: H5 is personal/team browser access layered on the existing desktop web UI, so the normal desktop app must keep its current layout behavior. Rejected: Share the global sidebarOpen default for mobile first paint | it can flash the drawer open before effects run on a phone. Confidence: high Scope-risk: moderate Directive: Keep mobile-only layout branching behind useMobileViewport() && !isTauriRuntime() unless a future task explicitly redesigns the native desktop shell. Tested: cd desktop && bunx vitest run src/hooks/useMobileViewport.test.tsx src/components/layout/AppShell.test.tsx src/components/layout/Sidebar.test.tsx src/pages/ActiveSession.test.tsx src/components/chat/ChatInput.test.tsx src/components/controls/PermissionModeSelector.test.tsx Tested: cd desktop && bun run lint Tested: cd desktop && bun run build
39 lines
1.1 KiB
TypeScript
39 lines
1.1 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
|
|
const MOBILE_VIEWPORT_QUERY = '(max-width: 767px)'
|
|
|
|
function getInitialMobileViewport() {
|
|
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false
|
|
return window.matchMedia(MOBILE_VIEWPORT_QUERY).matches
|
|
}
|
|
|
|
export function useMobileViewport() {
|
|
const [isMobile, setIsMobile] = useState(getInitialMobileViewport)
|
|
|
|
useEffect(() => {
|
|
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return
|
|
|
|
const mediaQuery = window.matchMedia(MOBILE_VIEWPORT_QUERY)
|
|
const handleChange = (event: MediaQueryListEvent) => {
|
|
setIsMobile(event.matches)
|
|
}
|
|
|
|
setIsMobile(mediaQuery.matches)
|
|
if (typeof mediaQuery.addEventListener === 'function') {
|
|
mediaQuery.addEventListener('change', handleChange)
|
|
} else {
|
|
mediaQuery.addListener(handleChange)
|
|
}
|
|
|
|
return () => {
|
|
if (typeof mediaQuery.removeEventListener === 'function') {
|
|
mediaQuery.removeEventListener('change', handleChange)
|
|
} else {
|
|
mediaQuery.removeListener(handleChange)
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
return isMobile
|
|
}
|