feat: add mobile layout with chat panel at bottom (#109)

This commit is contained in:
Dayuan Jiang
2025-12-05 23:25:59 +09:00
committed by GitHub
parent 4e32a094b1
commit 7205896c8c
2 changed files with 54 additions and 62 deletions

View File

@@ -3,7 +3,6 @@ import React, { useState, useEffect, useRef } from "react";
import { DrawIoEmbed } from "react-drawio"; import { DrawIoEmbed } from "react-drawio";
import ChatPanel from "@/components/chat-panel"; import ChatPanel from "@/components/chat-panel";
import { useDiagram } from "@/contexts/diagram-context"; import { useDiagram } from "@/contexts/diagram-context";
import { Monitor } from "lucide-react";
import { import {
ResizablePanelGroup, ResizablePanelGroup,
ResizablePanel, ResizablePanel,
@@ -74,29 +73,13 @@ export default function Home() {
return ( return (
<div className="h-screen bg-background relative overflow-hidden"> <div className="h-screen bg-background relative overflow-hidden">
{/* Mobile warning overlay */} <ResizablePanelGroup
{isMobile && ( direction={isMobile ? "vertical" : "horizontal"}
<div className="absolute inset-0 z-50 flex items-center justify-center bg-background"> className="h-full"
<div className="text-center p-8 max-w-sm mx-auto animate-fade-in"> >
<div className="w-16 h-16 rounded-2xl bg-primary/10 flex items-center justify-center mx-auto mb-6">
<Monitor className="w-8 h-8 text-primary" />
</div>
<h1 className="text-xl font-semibold text-foreground mb-3">
Desktop Required
</h1>
<p className="text-sm text-muted-foreground leading-relaxed">
This application works best on desktop or laptop
devices. Please open it on a larger screen for the
full experience.
</p>
</div>
</div>
)}
<ResizablePanelGroup direction="horizontal" className="h-full">
{/* Draw.io Canvas */} {/* Draw.io Canvas */}
<ResizablePanel defaultSize={67} minSize={30}> <ResizablePanel defaultSize={isMobile ? 50 : 67} minSize={20}>
<div className="h-full relative p-2"> <div className={`h-full relative ${isMobile ? "p-1" : "p-2"}`}>
<div className="h-full rounded-xl overflow-hidden shadow-soft-lg border border-border/30 bg-white"> <div className="h-full rounded-xl overflow-hidden shadow-soft-lg border border-border/30 bg-white">
<DrawIoEmbed <DrawIoEmbed
key={drawioUi} key={drawioUi}
@@ -119,15 +102,15 @@ export default function Home() {
{/* Chat Panel */} {/* Chat Panel */}
<ResizablePanel <ResizablePanel
ref={chatPanelRef} ref={chatPanelRef}
defaultSize={33} defaultSize={isMobile ? 50 : 33}
minSize={15} minSize={isMobile ? 20 : 15}
maxSize={50} maxSize={isMobile ? 80 : 50}
collapsible collapsible={!isMobile}
collapsedSize={3} collapsedSize={isMobile ? 0 : 3}
onCollapse={() => setIsChatVisible(false)} onCollapse={() => setIsChatVisible(false)}
onExpand={() => setIsChatVisible(true)} onExpand={() => setIsChatVisible(true)}
> >
<div className="h-full py-2 pr-2"> <div className={`h-full ${isMobile ? "p-1" : "py-2 pr-2"}`}>
<ChatPanel <ChatPanel
isVisible={isChatVisible} isVisible={isChatVisible}
onToggleVisibility={toggleChatPanel} onToggleVisibility={toggleChatPanel}
@@ -137,6 +120,7 @@ export default function Home() {
localStorage.setItem("drawio-theme", newTheme); localStorage.setItem("drawio-theme", newTheme);
setDrawioUi(newTheme); setDrawioUi(newTheme);
}} }}
isMobile={isMobile}
/> />
</div> </div>
</ResizablePanel> </ResizablePanel>

View File

@@ -31,6 +31,7 @@ interface ChatPanelProps {
onToggleVisibility: () => void; onToggleVisibility: () => void;
drawioUi: "min" | "sketch"; drawioUi: "min" | "sketch";
onToggleDrawioUi: () => void; onToggleDrawioUi: () => void;
isMobile?: boolean;
} }
export default function ChatPanel({ export default function ChatPanel({
@@ -38,6 +39,7 @@ export default function ChatPanel({
onToggleVisibility, onToggleVisibility,
drawioUi, drawioUi,
onToggleDrawioUi, onToggleDrawioUi,
isMobile = false,
}: ChatPanelProps) { }: ChatPanelProps) {
const { const {
loadDiagram: onDisplayChart, loadDiagram: onDisplayChart,
@@ -410,8 +412,8 @@ Please retry with an adjusted search pattern or use display_diagram if retries a
); );
}; };
// Collapsed view // Collapsed view (desktop only)
if (!isVisible) { if (!isVisible && !isMobile) {
return ( return (
<div className="h-full flex flex-col items-center pt-4 bg-card border border-border/30 rounded-xl"> <div className="h-full flex flex-col items-center pt-4 bg-card border border-border/30 rounded-xl">
<ButtonWithTooltip <ButtonWithTooltip
@@ -445,35 +447,39 @@ Please retry with an adjusted search pattern or use display_diagram if retries a
style={{ position: "absolute" }} style={{ position: "absolute" }}
/> />
{/* Header */} {/* Header */}
<header className="px-5 py-4 border-b border-border/50"> <header className={`${isMobile ? "px-3 py-2" : "px-5 py-4"} border-b border-border/50`}>
<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-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Image <Image
src="/favicon.ico" src="/favicon.ico"
alt="Next AI Drawio" alt="Next AI Drawio"
width={28} width={isMobile ? 24 : 28}
height={28} height={isMobile ? 24 : 28}
className="rounded" className="rounded"
/> />
<h1 className="text-base font-semibold tracking-tight whitespace-nowrap"> <h1 className={`${isMobile ? "text-sm" : "text-base"} font-semibold tracking-tight whitespace-nowrap`}>
Next AI Drawio Next AI Drawio
</h1> </h1>
</div> </div>
<Link {!isMobile && (
href="/about" <Link
className="text-sm text-muted-foreground hover:text-foreground transition-colors ml-2" href="/about"
> className="text-sm text-muted-foreground hover:text-foreground transition-colors ml-2"
About >
</Link> About
<ButtonWithTooltip </Link>
tooltipContent="Recent generation failures were caused by our AI provider's infrastructure issue, not the app code. After extensive debugging, I've switched providers and observed 6 hours of stability. If issues persist, please report on GitHub." )}
variant="ghost" {!isMobile && (
size="icon" <ButtonWithTooltip
className="h-6 w-6 text-green-500 hover:text-green-600" tooltipContent="Recent generation failures were caused by our AI provider's infrastructure issue, not the app code. After extensive debugging, I've switched providers and observed 6 hours of stability. If issues persist, please report on GitHub."
> variant="ghost"
<CheckCircle className="h-4 w-4" /> size="icon"
</ButtonWithTooltip> className="h-6 w-6 text-green-500 hover:text-green-600"
>
<CheckCircle className="h-4 w-4" />
</ButtonWithTooltip>
)}
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<a <a
@@ -482,7 +488,7 @@ Please retry with an adjusted search pattern or use display_diagram if retries a
rel="noopener noreferrer" rel="noopener noreferrer"
className="p-2 rounded-lg text-muted-foreground hover:text-foreground hover:bg-accent transition-colors" className="p-2 rounded-lg text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
> >
<FaGithub className="w-5 h-5" /> <FaGithub className={`${isMobile ? "w-4 h-4" : "w-5 h-5"}`} />
</a> </a>
{accessCodeRequired && ( {accessCodeRequired && (
<ButtonWithTooltip <ButtonWithTooltip
@@ -492,18 +498,20 @@ Please retry with an adjusted search pattern or use display_diagram if retries a
onClick={() => setShowSettingsDialog(true)} onClick={() => setShowSettingsDialog(true)}
className="hover:bg-accent" className="hover:bg-accent"
> >
<Settings className="h-5 w-5 text-muted-foreground" /> <Settings className={`${isMobile ? "h-4 w-4" : "h-5 w-5"} text-muted-foreground`} />
</ButtonWithTooltip>
)}
{!isMobile && (
<ButtonWithTooltip
tooltipContent="Hide chat panel (Ctrl+B)"
variant="ghost"
size="icon"
onClick={onToggleVisibility}
className="hover:bg-accent"
>
<PanelRightClose className="h-5 w-5 text-muted-foreground" />
</ButtonWithTooltip> </ButtonWithTooltip>
)} )}
<ButtonWithTooltip
tooltipContent="Hide chat panel (Ctrl+B)"
variant="ghost"
size="icon"
onClick={onToggleVisibility}
className="hover:bg-accent"
>
<PanelRightClose className="h-5 w-5 text-muted-foreground" />
</ButtonWithTooltip>
</div> </div>
</div> </div>
</header> </header>
@@ -521,7 +529,7 @@ Please retry with an adjusted search pattern or use display_diagram if retries a
</main> </main>
{/* Input */} {/* Input */}
<footer className="p-4 border-t border-border/50 bg-card/50"> <footer className={`${isMobile ? "p-2" : "p-4"} border-t border-border/50 bg-card/50`}>
<ChatInput <ChatInput
input={input} input={input}
status={status} status={status}