Enhance mobile responsiveness across all components
Comprehensive mobile improvements: - Toolbar: Added hamburger menu for mobile, larger touch targets - FileTabs: Taller tabs with always-visible close buttons on mobile - FileTree: Larger touch targets, bigger icons and buttons - ConsolePanel: Collapsed by default on mobile with toggle - Added touch-manipulation CSS for better tap performance - Responsive typography and spacing throughout
This commit is contained in:
parent
024ec42c5e
commit
68c881374d
4 changed files with 146 additions and 84 deletions
|
|
@ -38,6 +38,7 @@ function App() {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const [showPassportLogin, setShowPassportLogin] = useState(false);
|
const [showPassportLogin, setShowPassportLogin] = useState(false);
|
||||||
|
const [consoleCollapsed, setConsoleCollapsed] = useState(isMobile);
|
||||||
const [user, setUser] = useState<{ login: string; avatarUrl: string; email: string } | null>(() => {
|
const [user, setUser] = useState<{ login: string; avatarUrl: string; email: string } | null>(() => {
|
||||||
const stored = typeof window !== 'undefined' ? localStorage.getItem('aethex-user') : null;
|
const stored = typeof window !== 'undefined' ? localStorage.getItem('aethex-user') : null;
|
||||||
return stored ? JSON.parse(stored) : null;
|
return stored ? JSON.parse(stored) : null;
|
||||||
|
|
@ -446,7 +447,10 @@ end)`,
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
</div>
|
</div>
|
||||||
<ConsolePanel />
|
<ConsolePanel
|
||||||
|
collapsed={consoleCollapsed}
|
||||||
|
onToggle={() => setConsoleCollapsed(!consoleCollapsed)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{/* Unified feature tabs for all major panels */}
|
{/* Unified feature tabs for all major panels */}
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,12 @@ export function FileTabs({
|
||||||
if (openFiles.length === 0) return null;
|
if (openFiles.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center bg-card/70 border-b border-border h-8 min-h-8">
|
<div className="flex items-center bg-card/70 border-b border-border h-9 md:h-8 min-h-[36px] md:min-h-8">
|
||||||
<div className="flex overflow-x-auto flex-1">
|
<div className="flex overflow-x-auto flex-1 scrollbar-thin scrollbar-thumb-muted scrollbar-track-transparent">
|
||||||
{openFiles.map((file) => (
|
{openFiles.map((file) => (
|
||||||
<div
|
<div
|
||||||
key={file.id}
|
key={file.id}
|
||||||
className={`flex items-center gap-1 px-3 h-8 border-r border-border cursor-pointer group min-w-0 transition-colors select-none ${
|
className={`flex items-center gap-1 px-3 md:px-3 py-1 md:py-0 h-9 md:h-8 border-r border-border cursor-pointer group min-w-0 transition-colors select-none ${
|
||||||
activeFileId === file.id
|
activeFileId === file.id
|
||||||
? 'bg-background border-b-2 border-b-accent font-semibold text-accent'
|
? 'bg-background border-b-2 border-b-accent font-semibold text-accent'
|
||||||
: 'hover:bg-muted/60 text-muted-foreground'
|
: 'hover:bg-muted/60 text-muted-foreground'
|
||||||
|
|
@ -32,18 +32,19 @@ export function FileTabs({
|
||||||
onClick={() => onFileSelect(file)}
|
onClick={() => onFileSelect(file)}
|
||||||
title={file.name}
|
title={file.name}
|
||||||
>
|
>
|
||||||
<span className="text-xs truncate max-w-[100px]">{file.name}</span>
|
<span className="text-xs md:text-xs truncate max-w-[100px] md:max-w-[120px]">{file.name}</span>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-4 w-4 p-0 ml-1 opacity-0 group-hover:opacity-100 hover:text-destructive flex-shrink-0"
|
className="h-5 w-5 md:h-4 md:w-4 p-0 ml-1 opacity-70 md:opacity-0 group-hover:opacity-100 hover:text-destructive flex-shrink-0 touch-manipulation"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onFileClose(file.id);
|
onFileClose(file.id);
|
||||||
}}
|
}}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<X size={12} />
|
<X size={14} className="md:hidden" />
|
||||||
|
<X size={12} className="hidden md:block" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ export function FileTree({
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
onDrop={(e) => handleDrop(e, node)}
|
onDrop={(e) => handleDrop(e, node)}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
className={`flex items-center gap-1 px-2 py-1 hover:bg-muted/60 cursor-pointer group rounded-sm transition-colors ${
|
className={`flex items-center gap-1 px-2 py-1.5 md:py-1 hover:bg-muted/60 cursor-pointer group rounded-sm transition-colors touch-manipulation ${
|
||||||
isSelected ? 'bg-accent/30 text-accent font-semibold' : 'text-foreground'
|
isSelected ? 'bg-accent/30 text-accent font-semibold' : 'text-foreground'
|
||||||
} ${isDragging ? 'opacity-50' : ''} ${isDropTarget && node.type === 'folder' ? 'bg-blue-500/20 border-2 border-blue-500 border-dashed' : ''}`}
|
} ${isDragging ? 'opacity-50' : ''} ${isDropTarget && node.type === 'folder' ? 'bg-blue-500/20 border-2 border-blue-500 border-dashed' : ''}`}
|
||||||
style={{ paddingLeft: `${depth * 10 + 8}px` }}
|
style={{ paddingLeft: `${depth * 10 + 8}px` }}
|
||||||
|
|
@ -167,12 +167,12 @@ export function FileTree({
|
||||||
>
|
>
|
||||||
{node.type === 'folder' ? (
|
{node.type === 'folder' ? (
|
||||||
isExpanded ? (
|
isExpanded ? (
|
||||||
<FolderOpen size={15} className="flex-shrink-0 opacity-80" />
|
<FolderOpen size={16} className="flex-shrink-0 opacity-80 md:w-[15px] md:h-[15px]" />
|
||||||
) : (
|
) : (
|
||||||
<Folder size={15} className="flex-shrink-0 opacity-80" />
|
<Folder size={16} className="flex-shrink-0 opacity-80 md:w-[15px] md:h-[15px]" />
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<File size={15} className="flex-shrink-0 opacity-80" />
|
<File size={16} className="flex-shrink-0 opacity-80 md:w-[15px] md:h-[15px]" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
|
|
@ -197,9 +197,9 @@ export function FileTree({
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-5 w-5 opacity-0 group-hover:opacity-100"
|
className="h-6 w-6 md:h-5 md:w-5 opacity-50 md:opacity-0 group-hover:opacity-100 touch-manipulation"
|
||||||
>
|
>
|
||||||
<DotsThree size={13} />
|
<DotsThree size={16} className="md:w-[13px] md:h-[13px]" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { Copy, FileCode, Download, Info, Play, FolderPlus, User, SignOut } from '@phosphor-icons/react';
|
import { Copy, FileCode, Download, Info, Play, FolderPlus, User, SignOut, List } from '@phosphor-icons/react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
|
|
@ -56,92 +56,149 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-1 px-2 py-1.5 bg-card border-b border-border min-h-[38px]">
|
<div className="flex items-center gap-1 px-2 py-1.5 bg-card border-b border-border min-h-[38px] md:min-h-[42px]">
|
||||||
<h1 className="text-lg font-bold tracking-tight leading-none">
|
<h1 className="text-base md:text-lg font-bold tracking-tight leading-none">
|
||||||
Ae<span className="text-accent">Thex</span>
|
Ae<span className="text-accent">Thex</span>
|
||||||
</h1>
|
</h1>
|
||||||
<span className="text-xs text-muted-foreground ml-1 leading-none">Studio</span>
|
<span className="text-xs text-muted-foreground ml-1 leading-none">Studio</span>
|
||||||
|
|
||||||
<div className="flex-1" />
|
<div className="flex-1" />
|
||||||
|
|
||||||
|
{/* Desktop: Show all buttons */}
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<div className="hidden md:flex items-center gap-1">
|
||||||
<TooltipTrigger asChild>
|
<Tooltip>
|
||||||
<Button
|
<TooltipTrigger asChild>
|
||||||
variant="ghost"
|
<Button
|
||||||
size="icon"
|
variant="ghost"
|
||||||
onClick={onNewProjectClick}
|
size="icon"
|
||||||
className="p-1.5 rounded hover:bg-accent/10"
|
onClick={onNewProjectClick}
|
||||||
aria-label="New Project"
|
className="p-1.5 rounded hover:bg-accent/10"
|
||||||
>
|
aria-label="New Project"
|
||||||
<FolderPlus size={18} />
|
>
|
||||||
</Button>
|
<FolderPlus size={18} />
|
||||||
</TooltipTrigger>
|
</Button>
|
||||||
<TooltipContent>New Project</TooltipContent>
|
</TooltipTrigger>
|
||||||
</Tooltip>
|
<TooltipContent>New Project</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={onPreviewClick}
|
onClick={onPreviewClick}
|
||||||
className="p-1.5 rounded hover:bg-accent/10"
|
className="p-1.5 rounded hover:bg-accent/10"
|
||||||
aria-label="Preview"
|
aria-label="Preview"
|
||||||
>
|
>
|
||||||
<Play size={18} />
|
<Play size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Preview</TooltipContent>
|
<TooltipContent>Preview</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button variant="ghost" size="icon" onClick={onTemplatesClick} className="p-1.5 rounded hover:bg-accent/10" aria-label="Templates">
|
<Button variant="ghost" size="icon" onClick={onTemplatesClick} className="p-1.5 rounded hover:bg-accent/10" aria-label="Templates">
|
||||||
<FileCode size={18} />
|
<FileCode size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Templates</TooltipContent>
|
<TooltipContent>Templates</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button variant="ghost" size="icon" onClick={handleCopy} className="p-1.5 rounded hover:bg-accent/10" aria-label="Copy">
|
<Button variant="ghost" size="icon" onClick={handleCopy} className="p-1.5 rounded hover:bg-accent/10" aria-label="Copy">
|
||||||
<Copy size={18} />
|
<Copy size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Copy</TooltipContent>
|
<TooltipContent>Copy</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={handleExport}
|
onClick={handleExport}
|
||||||
className="p-1.5 rounded hover:bg-accent/10 hidden sm:flex"
|
className="p-1.5 rounded hover:bg-accent/10"
|
||||||
aria-label="Export"
|
aria-label="Export"
|
||||||
>
|
>
|
||||||
<Download size={18} />
|
<Download size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Export</TooltipContent>
|
<TooltipContent>Export</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button variant="ghost" size="icon" onClick={() => setShowInfo(true)} className="p-1.5 rounded hover:bg-accent/10" aria-label="About">
|
<Button variant="ghost" size="icon" onClick={() => setShowInfo(true)} className="p-1.5 rounded hover:bg-accent/10" aria-label="About">
|
||||||
<Info size={18} />
|
<Info size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>About</TooltipContent>
|
<TooltipContent>About</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile: Hamburger menu with essential actions */}
|
||||||
|
<div className="flex md:hidden items-center gap-1">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={onPreviewClick}
|
||||||
|
className="p-2 rounded hover:bg-accent/10"
|
||||||
|
aria-label="Preview"
|
||||||
|
>
|
||||||
|
<Play size={20} weight="bold" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Preview</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="p-2 rounded hover:bg-accent/10"
|
||||||
|
aria-label="Menu"
|
||||||
|
>
|
||||||
|
<List size={20} weight="bold" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-48">
|
||||||
|
<DropdownMenuItem onClick={onNewProjectClick}>
|
||||||
|
<FolderPlus className="mr-2" size={16} />
|
||||||
|
<span>New Project</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={onTemplatesClick}>
|
||||||
|
<FileCode className="mr-2" size={16} />
|
||||||
|
<span>Templates</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={handleCopy}>
|
||||||
|
<Copy className="mr-2" size={16} />
|
||||||
|
<span>Copy Code</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={handleExport}>
|
||||||
|
<Download className="mr-2" size={16} />
|
||||||
|
<span>Export</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem onClick={() => setShowInfo(true)}>
|
||||||
|
<Info className="mr-2" size={16} />
|
||||||
|
<span>About</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" size="icon" className="rounded-full p-0">
|
<Button variant="ghost" size="icon" className="rounded-full p-0 ml-1">
|
||||||
<Avatar className="h-7 w-7">
|
<Avatar className="h-7 w-7 md:h-8 md:w-8">
|
||||||
<AvatarImage src={user?.avatarUrl} alt={user?.login || 'User'} />
|
<AvatarImage src={user?.avatarUrl} alt={user?.login || 'User'} />
|
||||||
<AvatarFallback>
|
<AvatarFallback>
|
||||||
<User size={16} />
|
<User size={16} />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue