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
d1438192b8
commit
6bfabdb701
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 { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
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 { Link, Outlet, useLocation, useNavigate, useParams } from "@/lib/router";
|
||||||
import { CompanyRail } from "./CompanyRail";
|
import { CompanyRail } from "./CompanyRail";
|
||||||
import { Sidebar } from "./Sidebar";
|
import { Sidebar } from "./Sidebar";
|
||||||
import { InstanceSidebar } from "./InstanceSidebar";
|
import { InstanceSidebar } from "./InstanceSidebar";
|
||||||
import { BreadcrumbBar } from "./BreadcrumbBar";
|
import { BreadcrumbBar } from "./BreadcrumbBar";
|
||||||
|
import { ChatPanel } from "./ChatPanel";
|
||||||
import { PropertiesPanel } from "./PropertiesPanel";
|
import { PropertiesPanel } from "./PropertiesPanel";
|
||||||
import { CommandPalette } from "./CommandPalette";
|
import { CommandPalette } from "./CommandPalette";
|
||||||
import { NewIssueDialog } from "./NewIssueDialog";
|
import { NewIssueDialog } from "./NewIssueDialog";
|
||||||
|
|
@ -18,6 +19,7 @@ import { WorktreeBanner } from "./WorktreeBanner";
|
||||||
import { DevRestartBanner } from "./DevRestartBanner";
|
import { DevRestartBanner } from "./DevRestartBanner";
|
||||||
import { useDialog } from "../context/DialogContext";
|
import { useDialog } from "../context/DialogContext";
|
||||||
import { usePanel } from "../context/PanelContext";
|
import { usePanel } from "../context/PanelContext";
|
||||||
|
import { useChatPanel } from "../context/ChatPanelContext";
|
||||||
import { useCompany } from "../context/CompanyContext";
|
import { useCompany } from "../context/CompanyContext";
|
||||||
import { useSidebar } from "../context/SidebarContext";
|
import { useSidebar } from "../context/SidebarContext";
|
||||||
import { useTheme, THEME_META } from "../context/ThemeContext";
|
import { useTheme, THEME_META } from "../context/ThemeContext";
|
||||||
|
|
@ -49,7 +51,8 @@ function readRememberedInstanceSettingsPath(): string {
|
||||||
export function Layout() {
|
export function Layout() {
|
||||||
const { sidebarOpen, setSidebarOpen, toggleSidebar, isMobile } = useSidebar();
|
const { sidebarOpen, setSidebarOpen, toggleSidebar, isMobile } = useSidebar();
|
||||||
const { openNewIssue, openOnboarding } = useDialog();
|
const { openNewIssue, openOnboarding } = useDialog();
|
||||||
const { togglePanelVisible } = usePanel();
|
const { togglePanelVisible, setPanelVisible } = usePanel();
|
||||||
|
const { chatOpen, toggleChat } = useChatPanel();
|
||||||
const {
|
const {
|
||||||
companies,
|
companies,
|
||||||
loading: companiesLoading,
|
loading: companiesLoading,
|
||||||
|
|
@ -144,6 +147,13 @@ export function Layout() {
|
||||||
|
|
||||||
const togglePanel = togglePanelVisible;
|
const togglePanel = togglePanelVisible;
|
||||||
|
|
||||||
|
// Close PropertiesPanel when chat panel opens
|
||||||
|
useEffect(() => {
|
||||||
|
if (chatOpen) {
|
||||||
|
setPanelVisible(false);
|
||||||
|
}
|
||||||
|
}, [chatOpen, setPanelVisible]);
|
||||||
|
|
||||||
useCompanyPageMemory();
|
useCompanyPageMemory();
|
||||||
|
|
||||||
useKeyboardShortcuts({
|
useKeyboardShortcuts({
|
||||||
|
|
@ -389,6 +399,21 @@ export function Layout() {
|
||||||
<Settings className="h-4 w-4" />
|
<Settings className="h-4 w-4" />
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</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
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
@ -431,6 +456,7 @@ export function Layout() {
|
||||||
<Outlet />
|
<Outlet />
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
<ChatPanel />
|
||||||
<PropertiesPanel />
|
<PropertiesPanel />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { CompanyProvider } from "./context/CompanyContext";
|
||||||
import { LiveUpdatesProvider } from "./context/LiveUpdatesProvider";
|
import { LiveUpdatesProvider } from "./context/LiveUpdatesProvider";
|
||||||
import { BreadcrumbProvider } from "./context/BreadcrumbContext";
|
import { BreadcrumbProvider } from "./context/BreadcrumbContext";
|
||||||
import { PanelProvider } from "./context/PanelContext";
|
import { PanelProvider } from "./context/PanelContext";
|
||||||
|
import { ChatPanelProvider } from "./context/ChatPanelContext";
|
||||||
import { SidebarProvider } from "./context/SidebarContext";
|
import { SidebarProvider } from "./context/SidebarContext";
|
||||||
import { DialogProvider } from "./context/DialogContext";
|
import { DialogProvider } from "./context/DialogContext";
|
||||||
import { ToastProvider } from "./context/ToastContext";
|
import { ToastProvider } from "./context/ToastContext";
|
||||||
|
|
@ -48,11 +49,13 @@ createRoot(document.getElementById("root")!).render(
|
||||||
<BreadcrumbProvider>
|
<BreadcrumbProvider>
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<PanelProvider>
|
<PanelProvider>
|
||||||
<PluginLauncherProvider>
|
<ChatPanelProvider>
|
||||||
<DialogProvider>
|
<PluginLauncherProvider>
|
||||||
<App />
|
<DialogProvider>
|
||||||
</DialogProvider>
|
<App />
|
||||||
</PluginLauncherProvider>
|
</DialogProvider>
|
||||||
|
</PluginLauncherProvider>
|
||||||
|
</ChatPanelProvider>
|
||||||
</PanelProvider>
|
</PanelProvider>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
</BreadcrumbProvider>
|
</BreadcrumbProvider>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue