mirror of
https://github.com/aljazceru/claude-code-viewer.git
synced 2025-12-24 16:54:21 +01:00
feat: adjust response design
This commit is contained in:
@@ -172,7 +172,7 @@ export const SessionPageContent: FC<{
|
||||
ref={scrollContainerRef}
|
||||
className="flex-1 overflow-y-auto min-h-0"
|
||||
>
|
||||
<main className="w-full px-4 sm:px-8 md:px-12 lg:px-16 xl:px-20 pb-10 relative z-5">
|
||||
<main className="w-full px-4 sm:px-8 md:px-12 lg:px-16 xl:px-20 pb-20 sm:pb-10 relative z-5">
|
||||
<ConversationList
|
||||
conversations={conversations}
|
||||
getToolResult={getToolResult}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
"use client";
|
||||
|
||||
import { MessageSquareIcon, PlugIcon, SettingsIcon, XIcon } from "lucide-react";
|
||||
import { type FC, useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useProject } from "../../../../hooks/useProject";
|
||||
import { McpTab } from "./McpTab";
|
||||
import { SessionsTab } from "./SessionsTab";
|
||||
import { SettingsTab } from "./SettingsTab";
|
||||
|
||||
interface MobileSidebarProps {
|
||||
currentSessionId: string;
|
||||
projectId: string;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const MobileSidebar: FC<MobileSidebarProps> = ({
|
||||
currentSessionId,
|
||||
projectId,
|
||||
isOpen,
|
||||
onClose,
|
||||
}) => {
|
||||
const {
|
||||
data: { sessions },
|
||||
} = useProject(projectId);
|
||||
const [activeTab, setActiveTab] = useState<"sessions" | "mcp" | "settings">(
|
||||
"sessions",
|
||||
);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
// Handle portal mounting
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
// Handle escape key
|
||||
useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
document.addEventListener("keydown", handleEscape);
|
||||
// Prevent body scroll when sidebar is open
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleEscape);
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
const handleTabClick = (tab: "sessions" | "mcp" | "settings") => {
|
||||
setActiveTab(tab);
|
||||
};
|
||||
|
||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
switch (activeTab) {
|
||||
case "sessions":
|
||||
return (
|
||||
<SessionsTab
|
||||
sessions={sessions}
|
||||
currentSessionId={currentSessionId}
|
||||
projectId={projectId}
|
||||
/>
|
||||
);
|
||||
case "mcp":
|
||||
return <McpTab />;
|
||||
case "settings":
|
||||
return <SettingsTab />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
if (!mounted) return null;
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 transition-all duration-300 ease-out md:hidden",
|
||||
isOpen
|
||||
? "visible opacity-100"
|
||||
: "invisible opacity-0 pointer-events-none",
|
||||
)}
|
||||
>
|
||||
{/* Backdrop */}
|
||||
<button
|
||||
type="button"
|
||||
className="absolute inset-0 bg-black/50 backdrop-blur-sm cursor-default"
|
||||
onClick={handleBackdropClick}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
aria-label="Close sidebar"
|
||||
/>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute left-0 top-0 h-full w-80 max-w-[85vw] bg-sidebar text-sidebar-foreground transition-transform duration-300 ease-out flex",
|
||||
isOpen ? "translate-x-0" : "-translate-x-full",
|
||||
)}
|
||||
>
|
||||
{/* Tab Icons */}
|
||||
<div className="w-12 flex flex-col border-r border-sidebar-border bg-sidebar/50">
|
||||
<div className="flex flex-col p-2 space-y-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleTabClick("sessions")}
|
||||
className={cn(
|
||||
"w-8 h-8 flex items-center justify-center rounded-md transition-colors",
|
||||
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
||||
activeTab === "sessions"
|
||||
? "bg-sidebar-accent text-sidebar-accent-foreground shadow-sm"
|
||||
: "text-sidebar-foreground/70",
|
||||
)}
|
||||
>
|
||||
<MessageSquareIcon className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleTabClick("mcp")}
|
||||
className={cn(
|
||||
"w-8 h-8 flex items-center justify-center rounded-md transition-colors",
|
||||
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
||||
activeTab === "mcp"
|
||||
? "bg-sidebar-accent text-sidebar-accent-foreground shadow-sm"
|
||||
: "text-sidebar-foreground/70",
|
||||
)}
|
||||
>
|
||||
<PlugIcon className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleTabClick("settings")}
|
||||
className={cn(
|
||||
"w-8 h-8 flex items-center justify-center rounded-md transition-colors",
|
||||
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
||||
activeTab === "settings"
|
||||
? "bg-sidebar-accent text-sidebar-accent-foreground shadow-sm"
|
||||
: "text-sidebar-foreground/70",
|
||||
)}
|
||||
>
|
||||
<SettingsIcon className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content Area */}
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Header with close button */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-sidebar-border bg-sidebar/80 backdrop-blur-sm">
|
||||
<h2 className="text-lg font-semibold capitalize">{activeTab}</h2>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onClose}
|
||||
className="h-8 w-8 p-0 hover:bg-sidebar-accent/50"
|
||||
>
|
||||
<XIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="flex-1 overflow-hidden">{renderContent()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
};
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import { MessageSquareIcon, PlugIcon, SettingsIcon } from "lucide-react";
|
||||
import { type FC, useState } from "react";
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useProject } from "../../../../hooks/useProject";
|
||||
import { McpTab } from "./McpTab";
|
||||
import { MobileSidebar } from "./MobileSidebar";
|
||||
import { SessionsTab } from "./SessionsTab";
|
||||
import { SettingsTab } from "./SettingsTab";
|
||||
|
||||
@@ -133,17 +133,13 @@ export const SessionSidebar: FC<{
|
||||
{sidebarContent}
|
||||
</div>
|
||||
|
||||
{/* Mobile sidebar - rendered in dialog */}
|
||||
<div className="md:hidden">
|
||||
<Dialog open={isMobileOpen} onOpenChange={onMobileOpenChange}>
|
||||
<DialogContent
|
||||
className="p-0 max-w-sm w-full h-[85vh] max-h-[85vh] overflow-hidden flex flex-col top-4 left-[50%] translate-x-[-50%] translate-y-0"
|
||||
showCloseButton={false}
|
||||
>
|
||||
<div className="flex-1 overflow-hidden">{sidebarContent}</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
{/* Mobile sidebar */}
|
||||
<MobileSidebar
|
||||
currentSessionId={currentSessionId}
|
||||
projectId={projectId}
|
||||
isOpen={isMobileOpen}
|
||||
onClose={() => onMobileOpenChange?.(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user