mirror of
https://github.com/siteboon/claudecodeui.git
synced 2025-12-08 22:19:37 +00:00
fix: iOS PWA status bar overlap issue on mobile devices
- Add PWA detection logic to App.jsx - Apply dynamic padding to header and sidebar in PWA mode - Update viewport meta tag for better iOS compatibility - Change status bar style to black-translucent for PWA - Add CSS variables for safe area insets with fallback support This ensures menu button and sidebar content are properly visible below the iOS status bar when installed as a PWA.
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
<meta name="viewport" content="initial-scale=1, viewport-fit=cover, width=device-width" />
|
||||||
<title>Claude Code UI</title>
|
<title>Claude Code UI</title>
|
||||||
|
|
||||||
<!-- PWA Manifest -->
|
<!-- PWA Manifest -->
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<!-- iOS Safari PWA Meta Tags -->
|
<!-- iOS Safari PWA Meta Tags -->
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<meta name="apple-mobile-web-app-title" content="Claude UI" />
|
<meta name="apple-mobile-web-app-title" content="Claude UI" />
|
||||||
|
|
||||||
<!-- iOS Safari Icons -->
|
<!-- iOS Safari Icons -->
|
||||||
|
|||||||
34
src/App.jsx
34
src/App.jsx
@@ -77,6 +77,35 @@ function AppContent() {
|
|||||||
const [activeSessions, setActiveSessions] = useState(new Set()); // Track sessions with active conversations
|
const [activeSessions, setActiveSessions] = useState(new Set()); // Track sessions with active conversations
|
||||||
|
|
||||||
const { ws, sendMessage, messages } = useWebSocketContext();
|
const { ws, sendMessage, messages } = useWebSocketContext();
|
||||||
|
|
||||||
|
// Detect if running as PWA
|
||||||
|
const [isPWA, setIsPWA] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check if running in standalone mode (PWA)
|
||||||
|
const checkPWA = () => {
|
||||||
|
const isStandalone = window.matchMedia('(display-mode: standalone)').matches ||
|
||||||
|
window.navigator.standalone ||
|
||||||
|
document.referrer.includes('android-app://');
|
||||||
|
setIsPWA(isStandalone);
|
||||||
|
|
||||||
|
// Add class to body for CSS targeting
|
||||||
|
if (isStandalone) {
|
||||||
|
document.body.classList.add('pwa-mode');
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('pwa-mode');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkPWA();
|
||||||
|
|
||||||
|
// Listen for changes
|
||||||
|
window.matchMedia('(display-mode: standalone)').addEventListener('change', checkPWA);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.matchMedia('(display-mode: standalone)').removeEventListener('change', checkPWA);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkMobile = () => {
|
const checkMobile = () => {
|
||||||
@@ -561,6 +590,8 @@ function AppContent() {
|
|||||||
latestVersion={latestVersion}
|
latestVersion={latestVersion}
|
||||||
currentVersion={currentVersion}
|
currentVersion={currentVersion}
|
||||||
onShowVersionModal={() => setShowVersionModal(true)}
|
onShowVersionModal={() => setShowVersionModal(true)}
|
||||||
|
isPWA={isPWA}
|
||||||
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -606,6 +637,8 @@ function AppContent() {
|
|||||||
latestVersion={latestVersion}
|
latestVersion={latestVersion}
|
||||||
currentVersion={currentVersion}
|
currentVersion={currentVersion}
|
||||||
onShowVersionModal={() => setShowVersionModal(true)}
|
onShowVersionModal={() => setShowVersionModal(true)}
|
||||||
|
isPWA={isPWA}
|
||||||
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -622,6 +655,7 @@ function AppContent() {
|
|||||||
sendMessage={sendMessage}
|
sendMessage={sendMessage}
|
||||||
messages={messages}
|
messages={messages}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
|
isPWA={isPWA}
|
||||||
onMenuClick={() => setSidebarOpen(true)}
|
onMenuClick={() => setSidebarOpen(true)}
|
||||||
isLoading={isLoadingProjects}
|
isLoading={isLoadingProjects}
|
||||||
onInputFocusChange={setIsInputFocused}
|
onInputFocusChange={setIsInputFocused}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ function MainContent({
|
|||||||
sendMessage,
|
sendMessage,
|
||||||
messages,
|
messages,
|
||||||
isMobile,
|
isMobile,
|
||||||
|
isPWA,
|
||||||
onMenuClick,
|
onMenuClick,
|
||||||
isLoading,
|
isLoading,
|
||||||
onInputFocusChange,
|
onInputFocusChange,
|
||||||
@@ -152,10 +153,13 @@ function MainContent({
|
|||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
{/* Header with menu button for mobile */}
|
{/* Header with menu button for mobile */}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-3 sm:p-4 flex-shrink-0">
|
<div
|
||||||
|
className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-3 sm:p-4 pwa-header-safe flex-shrink-0"
|
||||||
|
style={isPWA && isMobile ? { paddingTop: '56px' } : {}}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
onClick={onMenuClick}
|
onClick={onMenuClick}
|
||||||
className="p-1.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"
|
className="p-1.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 pwa-menu-button"
|
||||||
>
|
>
|
||||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||||
@@ -188,10 +192,13 @@ function MainContent({
|
|||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
{/* Header with menu button for mobile */}
|
{/* Header with menu button for mobile */}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-3 sm:p-4 flex-shrink-0">
|
<div
|
||||||
|
className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-3 sm:p-4 pwa-header-safe flex-shrink-0"
|
||||||
|
style={isPWA && isMobile ? { paddingTop: '56px' } : {}}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
onClick={onMenuClick}
|
onClick={onMenuClick}
|
||||||
className="p-1.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"
|
className="p-1.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 pwa-menu-button"
|
||||||
>
|
>
|
||||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||||
@@ -224,7 +231,10 @@ function MainContent({
|
|||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
{/* Header with tabs */}
|
{/* Header with tabs */}
|
||||||
<div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-3 sm:p-4 flex-shrink-0">
|
<div
|
||||||
|
className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-3 sm:p-4 pwa-header-safe flex-shrink-0"
|
||||||
|
style={isPWA && isMobile ? { paddingTop: '56px' } : {}}
|
||||||
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center space-x-2 sm:space-x-3">
|
<div className="flex items-center space-x-2 sm:space-x-3">
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
@@ -234,7 +244,7 @@ function MainContent({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onMenuClick();
|
onMenuClick();
|
||||||
}}
|
}}
|
||||||
className="p-2.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 touch-manipulation active:scale-95"
|
className="p-2.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 touch-manipulation active:scale-95 pwa-menu-button"
|
||||||
>
|
>
|
||||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ function Sidebar({
|
|||||||
updateAvailable,
|
updateAvailable,
|
||||||
latestVersion,
|
latestVersion,
|
||||||
currentVersion,
|
currentVersion,
|
||||||
onShowVersionModal
|
onShowVersionModal,
|
||||||
|
isPWA,
|
||||||
|
isMobile
|
||||||
}) {
|
}) {
|
||||||
const [expandedProjects, setExpandedProjects] = useState(new Set());
|
const [expandedProjects, setExpandedProjects] = useState(new Set());
|
||||||
const [editingProject, setEditingProject] = useState(null);
|
const [editingProject, setEditingProject] = useState(null);
|
||||||
@@ -434,7 +436,10 @@ function Sidebar({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col bg-card md:select-none">
|
<div
|
||||||
|
className="h-full flex flex-col bg-card md:select-none"
|
||||||
|
style={isPWA && isMobile ? { paddingTop: '44px' } : {}}
|
||||||
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="md:p-4 md:border-b md:border-border">
|
<div className="md:p-4 md:border-b md:border-border">
|
||||||
{/* Desktop Header */}
|
{/* Desktop Header */}
|
||||||
@@ -479,7 +484,10 @@ function Sidebar({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Header */}
|
{/* Mobile Header */}
|
||||||
<div className="md:hidden p-3 border-b border-border">
|
<div
|
||||||
|
className="md:hidden p-3 border-b border-border"
|
||||||
|
style={isPWA && isMobile ? { paddingTop: '16px' } : {}}
|
||||||
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
|
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
|
||||||
|
|||||||
@@ -43,6 +43,22 @@
|
|||||||
--input: 214.3 31.8% 91.4%;
|
--input: 214.3 31.8% 91.4%;
|
||||||
--ring: 221.2 83.2% 53.3%;
|
--ring: 221.2 83.2% 53.3%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
|
|
||||||
|
/* Safe area CSS variables */
|
||||||
|
--safe-area-inset-top: env(safe-area-inset-top);
|
||||||
|
--safe-area-inset-right: env(safe-area-inset-right);
|
||||||
|
--safe-area-inset-bottom: env(safe-area-inset-bottom);
|
||||||
|
--safe-area-inset-left: env(safe-area-inset-left);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback for older iOS versions */
|
||||||
|
@supports (padding-top: constant(safe-area-inset-top)) {
|
||||||
|
:root {
|
||||||
|
--safe-area-inset-top: constant(safe-area-inset-top);
|
||||||
|
--safe-area-inset-right: constant(safe-area-inset-right);
|
||||||
|
--safe-area-inset-bottom: constant(safe-area-inset-bottom);
|
||||||
|
--safe-area-inset-left: constant(safe-area-inset-left);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
@@ -82,12 +98,46 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body, #root {
|
html, body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Root element with safe area padding for PWA */
|
||||||
|
#root {
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply safe area padding in standalone mode */
|
||||||
|
@supports (padding-top: env(safe-area-inset-top)) {
|
||||||
|
@media (display-mode: standalone) {
|
||||||
|
#root {
|
||||||
|
padding-top: var(--safe-area-inset-top);
|
||||||
|
padding-left: var(--safe-area-inset-left);
|
||||||
|
padding-right: var(--safe-area-inset-right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PWA mode detected by JavaScript - more reliable */
|
||||||
|
body.pwa-mode #root {
|
||||||
|
padding-top: calc(env(safe-area-inset-top, 0px) + 20px);
|
||||||
|
padding-left: env(safe-area-inset-left);
|
||||||
|
padding-right: env(safe-area-inset-right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* iOS specific PWA adjustments */
|
||||||
|
@supports (-webkit-touch-callout: none) {
|
||||||
|
body.pwa-mode #root {
|
||||||
|
/* iOS status bar is typically 20-44px */
|
||||||
|
padding-top: max(env(safe-area-inset-top, 44px), 44px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Global transition defaults */
|
/* Global transition defaults */
|
||||||
button,
|
button,
|
||||||
a,
|
a,
|
||||||
@@ -577,6 +627,17 @@
|
|||||||
padding-bottom: max(env(safe-area-inset-bottom), 12px);
|
padding-bottom: max(env(safe-area-inset-bottom), 12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* PWA specific header adjustments for iOS */
|
||||||
|
.pwa-header-safe {
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When PWA mode is detected by JavaScript */
|
||||||
|
body.pwa-mode .pwa-header-safe {
|
||||||
|
/* Reset padding since #root already handles safe area */
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.chat-input-mobile {
|
.chat-input-mobile {
|
||||||
padding-bottom: calc(60px + max(env(safe-area-inset-bottom), 12px));
|
padding-bottom: calc(60px + max(env(safe-area-inset-bottom), 12px));
|
||||||
|
|||||||
Reference in New Issue
Block a user