feat(21-04): create ChatPanel shell and wire into Layout and main.tsx
- ChatPanel: 380px right-side drawer with transition-[width] and hidden md:flex - Two-column skeleton: 160px conversation list + flex thread area with ChatInput - Layout: import ChatPanel, MessageSquare, useChatPanel; add chat toggle button - Layout: useEffect closes PropertiesPanel when chatOpen becomes true - Layout: ChatPanel rendered before PropertiesPanel in flex row - main.tsx: ChatPanelProvider wrapping app inside PanelProvider
This commit is contained in:
parent
fb423b4d66
commit
036dee3ef1
3 changed files with 99 additions and 7 deletions
63
ui/src/components/ChatPanel.tsx
Normal file
63
ui/src/components/ChatPanel.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { X } from "lucide-react";
|
||||
import { useChatPanel } from "../context/ChatPanelContext";
|
||||
import { ChatInput } from "./ChatInput";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
|
||||
export function ChatPanel() {
|
||||
const { chatOpen, setChatOpen } = useChatPanel();
|
||||
|
||||
return (
|
||||
<aside
|
||||
aria-label="Chat"
|
||||
className="hidden md:flex overflow-hidden transition-[width] duration-100 ease-out flex-shrink-0 border-l border-border flex-col bg-background"
|
||||
style={{ width: chatOpen ? 380 : 0 }}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b border-border px-4 py-2 min-w-[380px]">
|
||||
<span className="text-sm font-medium">Chat</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6"
|
||||
onClick={() => setChatOpen(false)}
|
||||
aria-label="Close chat"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Two-column layout: conversation list (left) + thread (right) */}
|
||||
<div className="flex flex-1 min-h-0 min-w-[380px]">
|
||||
{/* Left column: conversation list -- placeholder for Plan 05 */}
|
||||
<div className="w-[160px] flex-shrink-0 border-r border-border bg-card overflow-hidden">
|
||||
<div className="p-3 text-center text-xs text-muted-foreground">
|
||||
No conversations yet
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right column: message thread + input */}
|
||||
<div className="flex flex-1 flex-col min-w-0">
|
||||
<ScrollArea className="flex-1 p-3">
|
||||
{/* Messages placeholder -- wired in Plan 05 */}
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Send a message to start this conversation.
|
||||
</p>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
{/* Input area */}
|
||||
<div className="border-t border-border px-3 py-2">
|
||||
<ChatInput
|
||||
onSend={(content) => {
|
||||
// TODO: Wire to API in Plan 05
|
||||
console.log("send:", content);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { BookOpen, Moon, Settings, Sun } from "lucide-react";
|
||||
import { BookOpen, MessageSquare, Moon, Settings, Sun } from "lucide-react";
|
||||
import { Link, Outlet, useLocation, useNavigate, useParams } from "@/lib/router";
|
||||
import { CompanyRail } from "./CompanyRail";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
import { InstanceSidebar } from "./InstanceSidebar";
|
||||
import { BreadcrumbBar } from "./BreadcrumbBar";
|
||||
import { ChatPanel } from "./ChatPanel";
|
||||
import { PropertiesPanel } from "./PropertiesPanel";
|
||||
import { CommandPalette } from "./CommandPalette";
|
||||
import { NewIssueDialog } from "./NewIssueDialog";
|
||||
|
|
@ -19,6 +20,7 @@ import { DevRestartBanner } from "./DevRestartBanner";
|
|||
import { useDialog } from "../context/DialogContext";
|
||||
import { GeneralSettingsProvider } from "../context/GeneralSettingsContext";
|
||||
import { usePanel } from "../context/PanelContext";
|
||||
import { useChatPanel } from "../context/ChatPanelContext";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
import { useSidebar } from "../context/SidebarContext";
|
||||
import { useTheme, THEME_META } from "../context/ThemeContext";
|
||||
|
|
@ -51,7 +53,8 @@ function readRememberedInstanceSettingsPath(): string {
|
|||
export function Layout() {
|
||||
const { sidebarOpen, setSidebarOpen, toggleSidebar, isMobile } = useSidebar();
|
||||
const { openNewIssue, openOnboarding } = useDialog();
|
||||
const { togglePanelVisible } = usePanel();
|
||||
const { togglePanelVisible, setPanelVisible } = usePanel();
|
||||
const { chatOpen, toggleChat } = useChatPanel();
|
||||
const {
|
||||
companies,
|
||||
loading: companiesLoading,
|
||||
|
|
@ -150,6 +153,13 @@ export function Layout() {
|
|||
|
||||
const togglePanel = togglePanelVisible;
|
||||
|
||||
// Close PropertiesPanel when chat panel opens
|
||||
useEffect(() => {
|
||||
if (chatOpen) {
|
||||
setPanelVisible(false);
|
||||
}
|
||||
}, [chatOpen, setPanelVisible]);
|
||||
|
||||
useCompanyPageMemory();
|
||||
|
||||
useKeyboardShortcuts({
|
||||
|
|
@ -397,6 +407,21 @@ export function Layout() {
|
|||
<Settings className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
className="hidden md:inline-flex text-muted-foreground shrink-0"
|
||||
onClick={toggleChat}
|
||||
aria-label={chatOpen ? "Close chat" : "Open chat"}
|
||||
>
|
||||
<MessageSquare className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{chatOpen ? "Close chat" : "Open chat"}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
|
|
@ -439,6 +464,7 @@ export function Layout() {
|
|||
<Outlet />
|
||||
)}
|
||||
</main>
|
||||
<ChatPanel />
|
||||
<PropertiesPanel />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { CompanyProvider } from "./context/CompanyContext";
|
|||
import { LiveUpdatesProvider } from "./context/LiveUpdatesProvider";
|
||||
import { BreadcrumbProvider } from "./context/BreadcrumbContext";
|
||||
import { PanelProvider } from "./context/PanelContext";
|
||||
import { ChatPanelProvider } from "./context/ChatPanelContext";
|
||||
import { SidebarProvider } from "./context/SidebarContext";
|
||||
import { DialogProvider } from "./context/DialogContext";
|
||||
import { ToastProvider } from "./context/ToastContext";
|
||||
|
|
@ -48,11 +49,13 @@ createRoot(document.getElementById("root")!).render(
|
|||
<BreadcrumbProvider>
|
||||
<SidebarProvider>
|
||||
<PanelProvider>
|
||||
<PluginLauncherProvider>
|
||||
<DialogProvider>
|
||||
<App />
|
||||
</DialogProvider>
|
||||
</PluginLauncherProvider>
|
||||
<ChatPanelProvider>
|
||||
<PluginLauncherProvider>
|
||||
<DialogProvider>
|
||||
<App />
|
||||
</DialogProvider>
|
||||
</PluginLauncherProvider>
|
||||
</ChatPanelProvider>
|
||||
</PanelProvider>
|
||||
</SidebarProvider>
|
||||
</BreadcrumbProvider>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue