3 Commits

Author SHA1 Message Date
simos
69b7b59f00 style(ui): remove inline styles in favor of Tailwind classes
Replace inline height calculations and conditional PWA padding with
standardized Tailwind utilities (h-full) for improved consistency and
maintainability. Simplifies responsive layout by removing device-
specific style adjustments while maintaining visual appearance.
2025-11-07 03:51:44 +00:00
simos
51431832d8 style(ui): improve mobile responsiveness of status and navigation components
Refactor component spacing, typography, and layout to better support mobile
devices while maintaining desktop experience. Changes include:

- Reduce bottom margins and padding on mobile (mb-3 vs mb-6, px-2.5 vs px-4)
- Use responsive text sizes (text-xs sm:text-sm, text-base sm:text-xl)
- Add truncation and min-w-0 to prevent text overflow on small screens
- Hide non-essential info on mobile (token counts, keyboard shortcuts)
- Adjust gap spacing for tighter mobile layouts (gap-1.5 vs gap-3)
- Improve button touch targets with refined padding
- Update background colors for better contrast (gray-800 vs gray-900)
- Add border styling for enhanced component definition

Affects ClaudeStatus, CodeEditor, GitPanel, MainContent, MobileNav,
QuickSettingsPanel components and global styles. Ensures consistent
mobile-first design across the application.
2025-11-07 03:39:15 +00:00
simos
1e50cfdad6 style: standardize mobile navigation spacing with Tailwind utility 2025-11-06 20:31:32 +00:00
11 changed files with 98 additions and 113 deletions

View File

@@ -744,7 +744,7 @@ function AppContent() {
{/* Fixed Desktop Sidebar */} {/* Fixed Desktop Sidebar */}
{!isMobile && ( {!isMobile && (
<div <div
className={`flex-shrink-0 border-r border-border bg-card transition-all duration-300 ${ className={`h-full flex-shrink-0 border-r border-border bg-card transition-all duration-300 ${
sidebarVisible ? 'w-80' : 'w-14' sidebarVisible ? 'w-80' : 'w-14'
}`} }`}
> >
@@ -838,10 +838,9 @@ function AppContent() {
aria-label="Close sidebar" aria-label="Close sidebar"
/> />
<div <div
className={`relative w-[85vw] max-w-sm sm:w-80 bg-card border-r border-border transform transition-transform duration-150 ease-out ${ className={`relative w-[85vw] max-w-sm sm:w-80 h-full bg-card border-r border-border transform transition-transform duration-150 ease-out ${
sidebarOpen ? 'translate-x-0' : '-translate-x-full' sidebarOpen ? 'translate-x-0' : '-translate-x-full'
}`} }`}
style={{ height: 'calc(100vh - 80px)' }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()} onTouchStart={(e) => e.stopPropagation()}
> >
@@ -871,7 +870,7 @@ function AppContent() {
)} )}
{/* Main Content Area - Flexible */} {/* Main Content Area - Flexible */}
<div className={`flex-1 flex flex-col min-w-0 ${isMobile && !isInputFocused ? 'pb-16' : ''}`}> <div className={`flex-1 flex flex-col min-w-0 ${isMobile && !isInputFocused ? 'pb-mobile-nav' : ''}`}>
<MainContent <MainContent
selectedProject={selectedProject} selectedProject={selectedProject}
selectedSession={selectedSession} selectedSession={selectedSession}

View File

@@ -4743,7 +4743,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
</svg> </svg>
</button> </button>
{/* Hint text inside input box at bottom */} {/* Hint text inside input box at bottom - Desktop only */}
<div className={`absolute bottom-1 left-12 right-14 sm:right-40 text-xs text-gray-400 dark:text-gray-500 pointer-events-none hidden sm:block transition-opacity duration-200 ${ <div className={`absolute bottom-1 left-12 right-14 sm:right-40 text-xs text-gray-400 dark:text-gray-500 pointer-events-none hidden sm:block transition-opacity duration-200 ${
input.trim() ? 'opacity-0' : 'opacity-100' input.trim() ? 'opacity-0' : 'opacity-100'
}`}> }`}>
@@ -4751,13 +4751,6 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
? "Ctrl+Enter to send • Shift+Enter for new line • Tab to change modes • / for slash commands" ? "Ctrl+Enter to send • Shift+Enter for new line • Tab to change modes • / for slash commands"
: "Enter to send • Shift+Enter for new line • Tab to change modes • / for slash commands"} : "Enter to send • Shift+Enter for new line • Tab to change modes • / for slash commands"}
</div> </div>
<div className={`absolute bottom-1 left-12 right-14 text-xs text-gray-400 dark:text-gray-500 pointer-events-none sm:hidden transition-opacity duration-200 ${
isInputFocused && !input.trim() ? 'opacity-100' : 'opacity-0'
}`}>
{sendByCtrlEnter
? "Ctrl+Enter to send • Tab for modes • / for commands"
: "Enter to send • Tab for modes • / for commands"}
</div>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -57,36 +57,31 @@ function ClaudeStatus({ status, onAbort, isLoading, provider = 'claude' }) {
const currentSpinner = spinners[animationPhase]; const currentSpinner = spinners[animationPhase];
return ( return (
<div className="w-full mb-6 animate-in slide-in-from-bottom duration-300"> <div className="w-full mb-3 sm:mb-6 animate-in slide-in-from-bottom duration-300">
<div className="flex items-center justify-between max-w-4xl mx-auto bg-gray-900 dark:bg-gray-950 text-white rounded-lg shadow-lg px-4 py-3"> <div className="flex items-center justify-between max-w-4xl mx-auto bg-gray-800 dark:bg-gray-900 text-white rounded-lg shadow-lg px-2.5 py-2 sm:px-4 sm:py-3 border border-gray-700 dark:border-gray-800">
<div className="flex-1"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-3"> <div className="flex items-center gap-2 sm:gap-3">
{/* Animated spinner */} {/* Animated spinner */}
<span className={cn( <span className={cn(
"text-xl transition-all duration-500", "text-base sm:text-xl transition-all duration-500 flex-shrink-0",
animationPhase % 2 === 0 ? "text-blue-400 scale-110" : "text-blue-300" animationPhase % 2 === 0 ? "text-blue-400 scale-110" : "text-blue-300"
)}> )}>
{currentSpinner} {currentSpinner}
</span> </span>
{/* Status text - first line */} {/* Status text - compact for mobile */}
<div className="flex-1"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2"> <div className="flex items-center gap-1.5 sm:gap-2">
<span className="font-medium text-sm">{statusText}...</span> <span className="font-medium text-xs sm:text-sm truncate">{statusText}...</span>
<span className="text-gray-400 text-sm">({elapsedTime}s)</span> <span className="text-gray-400 text-xs sm:text-sm flex-shrink-0">({elapsedTime}s)</span>
{tokens > 0 && ( {tokens > 0 && (
<> <>
<span className="text-gray-400">·</span> <span className="text-gray-500 hidden sm:inline">·</span>
<span className="text-gray-300 text-sm hidden sm:inline"> {tokens.toLocaleString()} tokens</span> <span className="text-gray-300 text-xs sm:text-sm hidden sm:inline flex-shrink-0"> {tokens.toLocaleString()}</span>
<span className="text-gray-300 text-sm sm:hidden"> {tokens.toLocaleString()}</span>
</> </>
)} )}
<span className="text-gray-400 hidden sm:inline">·</span> <span className="text-gray-500 hidden sm:inline">·</span>
<span className="text-gray-300 text-sm hidden sm:inline">esc to interrupt</span> <span className="text-gray-400 text-xs sm:text-sm hidden sm:inline">esc to stop</span>
</div>
{/* Second line for mobile */}
<div className="text-xs text-gray-400 sm:hidden mt-1">
esc to interrupt
</div> </div>
</div> </div>
</div> </div>
@@ -96,7 +91,7 @@ function ClaudeStatus({ status, onAbort, isLoading, provider = 'claude' }) {
{canInterrupt && onAbort && ( {canInterrupt && onAbort && (
<button <button
onClick={onAbort} onClick={onAbort}
className="ml-3 text-xs bg-red-600 hover:bg-red-700 text-white px-2.5 py-1 sm:px-3 sm:py-1.5 rounded-md transition-colors flex items-center gap-1.5 flex-shrink-0" className="ml-2 sm:ml-3 text-xs bg-red-600 hover:bg-red-700 active:bg-red-800 text-white px-2 py-1 sm:px-3 sm:py-1.5 rounded-md transition-colors flex items-center gap-1 sm:gap-1.5 flex-shrink-0 font-medium"
> >
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />

View File

@@ -460,7 +460,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
`} `}
</style> </style>
{isSidebar ? ( {isSidebar ? (
<div className="w-full h-full flex items-center justify-center bg-white dark:bg-gray-900"> <div className="w-full h-full flex items-center justify-center bg-background">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div> <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
<span className="text-gray-900 dark:text-white">Loading {file.name}...</span> <span className="text-gray-900 dark:text-white">Loading {file.name}...</span>
@@ -560,14 +560,14 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
'md:bg-black/50 md:flex md:items-center md:justify-center md:p-4' 'md:bg-black/50 md:flex md:items-center md:justify-center md:p-4'
} ${isFullscreen ? 'md:p-0' : ''}`}> } ${isFullscreen ? 'md:p-0' : ''}`}>
<div className={isSidebar ? <div className={isSidebar ?
'bg-white dark:bg-gray-900 flex flex-col w-full h-full' : 'bg-background flex flex-col w-full h-full' :
`bg-white shadow-2xl flex flex-col ${ `bg-background shadow-2xl flex flex-col ${
// Mobile: always fullscreen, Desktop: modal sizing // Mobile: always fullscreen, Desktop: modal sizing
'w-full h-full md:rounded-lg md:shadow-2xl' + 'w-full h-full md:rounded-lg md:shadow-2xl' +
(isFullscreen ? ' md:w-full md:h-full md:rounded-none' : ' md:w-full md:max-w-6xl md:h-[80vh] md:max-h-[80vh]') (isFullscreen ? ' md:w-full md:h-full md:rounded-none' : ' md:w-full md:max-w-6xl md:h-[80vh] md:max-h-[80vh]')
}`}> }`}>
{/* Header */} {/* Header */}
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0 min-w-0"> <div className="flex items-center justify-between p-4 border-b border-border flex-shrink-0 min-w-0">
<div className="flex items-center gap-3 min-w-0 flex-1"> <div className="flex items-center gap-3 min-w-0 flex-1">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
@@ -684,7 +684,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
</div> </div>
{/* Footer */} {/* Footer */}
<div className="flex items-center justify-between p-3 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 flex-shrink-0"> <div className="flex items-center justify-between p-3 border-t border-border bg-muted flex-shrink-0">
<div className="flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400"> <div className="flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400">
<span>Lines: {content.split('\n').length}</span> <span>Lines: {content.split('\n').length}</span>
<span>Characters: {content.length}</span> <span>Characters: {content.length}</span>

View File

@@ -769,7 +769,7 @@ function GitPanel({ selectedProject, isMobile, onFileOpen }) {
} }
return ( return (
<div className="h-full flex flex-col bg-white dark:bg-gray-900"> <div className="h-full flex flex-col bg-background">
{/* Header */} {/* Header */}
<div className={`flex items-center justify-between border-b border-gray-200 dark:border-gray-700 ${isMobile ? 'px-3 py-2' : 'px-4 py-3'}`}> <div className={`flex items-center justify-between border-b border-gray-200 dark:border-gray-700 ${isMobile ? 'px-3 py-2' : 'px-4 py-3'}`}>
<div className="relative" ref={dropdownRef}> <div className="relative" ref={dropdownRef}>
@@ -1156,7 +1156,7 @@ function GitPanel({ selectedProject, isMobile, onFileOpen }) {
{/* File List - Changes View - Only show when git is available */} {/* File List - Changes View - Only show when git is available */}
{activeView === 'changes' && !gitStatus?.error && ( {activeView === 'changes' && !gitStatus?.error && (
<div className={`flex-1 overflow-y-auto ${isMobile ? 'pb-20' : ''}`}> <div className={`flex-1 overflow-y-auto ${isMobile ? 'pb-mobile-nav' : ''}`}>
{isLoading ? ( {isLoading ? (
<div className="flex items-center justify-center h-32"> <div className="flex items-center justify-center h-32">
<RefreshCw className="w-6 h-6 animate-spin text-gray-400" /> <RefreshCw className="w-6 h-6 animate-spin text-gray-400" />
@@ -1179,7 +1179,7 @@ function GitPanel({ selectedProject, isMobile, onFileOpen }) {
{/* History View - Only show when git is available */} {/* History View - Only show when git is available */}
{activeView === 'history' && !gitStatus?.error && ( {activeView === 'history' && !gitStatus?.error && (
<div className={`flex-1 overflow-y-auto ${isMobile ? 'pb-20' : ''}`}> <div className={`flex-1 overflow-y-auto ${isMobile ? 'pb-mobile-nav' : ''}`}>
{isLoading ? ( {isLoading ? (
<div className="flex items-center justify-center h-32"> <div className="flex items-center justify-center h-32">
<RefreshCw className="w-6 h-6 animate-spin text-gray-400" /> <RefreshCw className="w-6 h-6 animate-spin text-gray-400" />

View File

@@ -214,11 +214,11 @@ function MainContent({
{/* Header with menu button for mobile */} {/* Header with menu button for mobile */}
{isMobile && ( {isMobile && (
<div <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" className="bg-background border-b border-border p-2 sm:p-3 pwa-header-safe flex-shrink-0"
> >
<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 pwa-menu-button" className="p-2 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" />
@@ -252,11 +252,11 @@ function MainContent({
{/* Header with menu button for mobile */} {/* Header with menu button for mobile */}
{isMobile && ( {isMobile && (
<div <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" className="bg-background border-b border-border p-2 sm:p-3 pwa-header-safe flex-shrink-0"
> >
<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 pwa-menu-button" className="p-2 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" />
@@ -290,10 +290,10 @@ function MainContent({
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
{/* Header with tabs */} {/* Header with tabs */}
<div <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" className="bg-background border-b border-border p-2 sm:p-3 pwa-header-safe flex-shrink-0"
> >
<div className="flex items-center justify-between relative"> <div className="flex items-center justify-between relative">
<div className="flex items-center space-x-2 sm:space-x-3"> <div className="flex items-center space-x-2 min-w-0 flex-1">
{isMobile && ( {isMobile && (
<button <button
onClick={onMenuClick} onClick={onMenuClick}
@@ -301,36 +301,36 @@ 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 pwa-menu-button" className="p-2 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 flex-shrink-0"
> >
<svg className="w-6 h-6" 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" />
</svg> </svg>
</button> </button>
)} )}
<div className="min-w-0 flex items-center gap-2"> <div className="min-w-0 flex items-center gap-2 flex-1 overflow-x-auto scrollbar-hide">
{activeTab === 'chat' && selectedSession && ( {activeTab === 'chat' && selectedSession && (
<div className="w-6 h-6 flex-shrink-0 flex items-center justify-center"> <div className="w-5 h-5 flex-shrink-0 flex items-center justify-center">
{selectedSession.__provider === 'cursor' ? ( {selectedSession.__provider === 'cursor' ? (
<CursorLogo className="w-5 h-5" /> <CursorLogo className="w-4 h-4" />
) : ( ) : (
<ClaudeLogo className="w-5 h-5" /> <ClaudeLogo className="w-4 h-4" />
)} )}
</div> </div>
)} )}
<div className="flex-1 min-w-0"> <div className="min-w-0 flex-1">
{activeTab === 'chat' && selectedSession ? ( {activeTab === 'chat' && selectedSession ? (
<div> <div className="min-w-0">
<h2 className="text-base sm:text-lg font-semibold text-gray-900 dark:text-white truncate"> <h2 className="text-sm sm:text-base font-semibold text-gray-900 dark:text-white whitespace-nowrap overflow-x-auto scrollbar-hide">
{selectedSession.__provider === 'cursor' ? (selectedSession.name || 'Untitled Session') : (selectedSession.summary || 'New Session')} {selectedSession.__provider === 'cursor' ? (selectedSession.name || 'Untitled Session') : (selectedSession.summary || 'New Session')}
</h2> </h2>
<div className="text-xs text-gray-500 dark:text-gray-400 truncate"> <div className="text-xs text-gray-500 dark:text-gray-400 truncate">
{selectedProject.displayName} <span className="hidden sm:inline"> {selectedSession.id}</span> {selectedProject.displayName}
</div> </div>
</div> </div>
) : activeTab === 'chat' && !selectedSession ? ( ) : activeTab === 'chat' && !selectedSession ? (
<div> <div className="min-w-0">
<h2 className="text-base sm:text-lg font-semibold text-gray-900 dark:text-white"> <h2 className="text-sm sm:text-base font-semibold text-gray-900 dark:text-white">
New Session New Session
</h2> </h2>
<div className="text-xs text-gray-500 dark:text-gray-400 truncate"> <div className="text-xs text-gray-500 dark:text-gray-400 truncate">
@@ -338,8 +338,8 @@ function MainContent({
</div> </div>
</div> </div>
) : ( ) : (
<div> <div className="min-w-0">
<h2 className="text-base sm:text-lg font-semibold text-gray-900 dark:text-white"> <h2 className="text-sm sm:text-base font-semibold text-gray-900 dark:text-white">
{activeTab === 'files' ? 'Project Files' : {activeTab === 'files' ? 'Project Files' :
activeTab === 'git' ? 'Source Control' : activeTab === 'git' ? 'Source Control' :
(activeTab === 'tasks' && shouldShowTasksTab) ? 'TaskMaster' : (activeTab === 'tasks' && shouldShowTasksTab) ? 'TaskMaster' :

View File

@@ -4,8 +4,6 @@ import { useTasksSettings } from '../contexts/TasksSettingsContext';
function MobileNav({ activeTab, setActiveTab, isInputFocused }) { function MobileNav({ activeTab, setActiveTab, isInputFocused }) {
const { tasksEnabled } = useTasksSettings(); const { tasksEnabled } = useTasksSettings();
// Detect dark mode
const isDarkMode = document.documentElement.classList.contains('dark');
const navItems = [ const navItems = [
{ {
id: 'chat', id: 'chat',
@@ -36,22 +34,11 @@ function MobileNav({ activeTab, setActiveTab, isInputFocused }) {
]; ];
return ( return (
<> <div
<style> className={`fixed bottom-0 left-0 right-0 bg-background border-t border-border z-50 ios-bottom-safe transform transition-transform duration-300 ease-in-out shadow-lg ${
{` isInputFocused ? 'translate-y-full' : 'translate-y-0'
.mobile-nav-container { }`}
background-color: ${isDarkMode ? '#1f2937' : '#ffffff'} !important; >
}
.mobile-nav-container:hover {
background-color: ${isDarkMode ? '#1f2937' : '#ffffff'} !important;
}
`}
</style>
<div
className={`mobile-nav-container fixed bottom-0 left-0 right-0 border-t border-gray-200 dark:border-gray-700 z-50 ios-bottom-safe transform transition-transform duration-300 ease-in-out shadow-lg ${
isInputFocused ? 'translate-y-full' : 'translate-y-0'
}`}
>
<div className="flex items-center justify-around py-1"> <div className="flex items-center justify-around py-1">
{navItems.map((item) => { {navItems.map((item) => {
const Icon = item.icon; const Icon = item.icon;
@@ -81,7 +68,6 @@ function MobileNav({ activeTab, setActiveTab, isInputFocused }) {
})} })}
</div> </div>
</div> </div>
</>
); );
} }

View File

@@ -71,7 +71,7 @@ const QuickSettingsPanel = ({
{/* Panel */} {/* Panel */}
<div <div
className={`fixed top-0 right-0 h-full w-64 bg-white dark:bg-gray-900 border-l border-gray-200 dark:border-gray-700 shadow-xl transform transition-transform duration-150 ease-out z-40 ${ className={`fixed top-0 right-0 h-full w-64 bg-background border-l border-border shadow-xl transform transition-transform duration-150 ease-out z-40 ${
localIsOpen ? 'translate-x-0' : 'translate-x-full' localIsOpen ? 'translate-x-0' : 'translate-x-full'
} ${isMobile ? 'h-screen' : ''}`} } ${isMobile ? 'h-screen' : ''}`}
> >
@@ -85,7 +85,7 @@ const QuickSettingsPanel = ({
</div> </div>
{/* Settings Content */} {/* Settings Content */}
<div className={`flex-1 overflow-y-auto overflow-x-hidden p-4 space-y-6 bg-white dark:bg-gray-900 ${isMobile ? 'pb-20' : ''}`}> <div className={`flex-1 overflow-y-auto overflow-x-hidden p-4 space-y-6 bg-background ${isMobile ? 'pb-mobile-nav' : ''}`}>
{/* Appearance Settings */} {/* Appearance Settings */}
<div className="space-y-2"> <div className="space-y-2">
<h4 className="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-2">Appearance</h4> <h4 className="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-2">Appearance</h4>

View File

@@ -467,7 +467,6 @@ function Sidebar({
<div <div
className="h-full flex flex-col bg-card md:select-none" 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">
@@ -505,7 +504,6 @@ function Sidebar({
{/* Mobile Header */} {/* Mobile Header */}
<div <div
className="md:hidden p-3 border-b border-border" 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">
@@ -1308,7 +1306,7 @@ function Sidebar({
{/* Settings Section */} {/* Settings Section */}
<div className="md:p-2 md:border-t md:border-border flex-shrink-0"> <div className="md:p-2 md:border-t md:border-border flex-shrink-0">
{/* Mobile Settings */} {/* Mobile Settings */}
<div className="md:hidden p-4 pb-20 border-t border-border/50"> <div className="md:hidden p-4 pb-mobile-nav border-t border-border/50">
<button <button
className="w-full h-14 bg-muted/50 hover:bg-muted/70 rounded-2xl flex items-center justify-start gap-4 px-4 active:scale-[0.98] transition-all duration-150" className="w-full h-14 bg-muted/50 hover:bg-muted/70 rounded-2xl flex items-center justify-start gap-4 px-4 active:scale-[0.98] transition-all duration-150"
onClick={onShowSettings} onClick={onShowSettings}

View File

@@ -49,6 +49,16 @@
--safe-area-inset-right: env(safe-area-inset-right); --safe-area-inset-right: env(safe-area-inset-right);
--safe-area-inset-bottom: env(safe-area-inset-bottom); --safe-area-inset-bottom: env(safe-area-inset-bottom);
--safe-area-inset-left: env(safe-area-inset-left); --safe-area-inset-left: env(safe-area-inset-left);
/* Mobile navigation dimensions - Single source of truth */
--mobile-nav-height: 60px;
--mobile-nav-padding: 12px;
--mobile-nav-total: calc(var(--mobile-nav-height) + max(env(safe-area-inset-bottom, 0px), var(--mobile-nav-padding)));
/* Header safe area dimensions */
--header-safe-area-top: env(safe-area-inset-top, 0px);
--header-base-padding: 8px;
--header-total-padding: calc(var(--header-safe-area-top) + var(--header-base-padding));
} }
/* Fallback for older iOS versions */ /* Fallback for older iOS versions */
@@ -128,30 +138,24 @@
body.pwa-mode { body.pwa-mode {
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
background-color: rgb(255 255 255); /* white - same as bg-white for safe area */
} }
body.pwa-mode #root { body.pwa-mode #root {
padding-top: calc(env(safe-area-inset-top, 0px) + 8px); padding-top: var(--header-total-padding);
padding-left: env(safe-area-inset-left); padding-left: var(--safe-area-inset-left);
padding-right: env(safe-area-inset-right); padding-right: var(--safe-area-inset-right);
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
} }
/* Adjust fixed inset positioning in PWA mode */ /* Adjust fixed inset positioning in PWA mode */
body.pwa-mode .fixed.inset-0 { body.pwa-mode .fixed.inset-0 {
top: calc(env(safe-area-inset-top, 0px) + 8px); top: var(--header-total-padding);
left: env(safe-area-inset-left); left: var(--safe-area-inset-left);
right: env(safe-area-inset-right); right: var(--safe-area-inset-right);
bottom: 0; bottom: 0;
} }
/* Dark mode safe area background */
html.dark body.pwa-mode {
background-color: rgb(31 41 55); /* gray-800 - matches header color */
}
/* Global transition defaults */ /* Global transition defaults */
button, button,
a, a,
@@ -641,9 +645,9 @@
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 specific header adjustments - uses CSS variables for consistency */
.pwa-header-safe { .pwa-header-safe {
padding-top: 16px; padding-top: var(--header-base-padding);
} }
/* When PWA mode is detected by JavaScript */ /* When PWA mode is detected by JavaScript */
@@ -652,11 +656,10 @@
padding-top: 0px !important; padding-top: 0px !important;
} }
/* For mobile PWA, ensure proper header spacing */ /* For mobile PWA, add bottom padding for better spacing */
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
body.pwa-mode .pwa-header-safe { body.pwa-mode .pwa-header-safe {
padding-top: 4px !important; padding-bottom: 8px;
padding-bottom: 12px;
} }
} }
@@ -686,6 +689,16 @@
max-width: 100%; max-width: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
/* Hide scrollbar utility for horizontal scroll */
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
} }
/* Hide markdown backticks in prose content */ /* Hide markdown backticks in prose content */

View File

@@ -56,6 +56,7 @@ export default {
}, },
spacing: { spacing: {
'safe-area-inset-bottom': 'env(safe-area-inset-bottom)', 'safe-area-inset-bottom': 'env(safe-area-inset-bottom)',
'mobile-nav': 'var(--mobile-nav-total)',
}, },
}, },
}, },