Use 100dvh for proper mobile browser chrome handling and increase cat icon sizes across sidebar, mobile header, and empty state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
92 lines
2.7 KiB
TypeScript
92 lines
2.7 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
import { Plus } from "lucide-react";
|
|
import { cn } from "../lib/utils";
|
|
import { conversationService } from "../api/conversationService";
|
|
|
|
type Conversation = {
|
|
title: string;
|
|
id: string;
|
|
};
|
|
|
|
type ConversationProps = {
|
|
conversations: Conversation[];
|
|
onSelectConversation: (conversation: Conversation) => void;
|
|
onCreateNewConversation: () => void;
|
|
selectedId?: string;
|
|
variant?: "dark" | "light";
|
|
};
|
|
|
|
export const ConversationList = ({
|
|
conversations,
|
|
onSelectConversation,
|
|
onCreateNewConversation,
|
|
selectedId,
|
|
variant = "dark",
|
|
}: ConversationProps) => {
|
|
const [items, setItems] = useState(conversations);
|
|
|
|
useEffect(() => {
|
|
const load = async () => {
|
|
try {
|
|
let fetched = await conversationService.getAllConversations();
|
|
if (fetched.length === 0) {
|
|
await conversationService.createConversation();
|
|
fetched = await conversationService.getAllConversations();
|
|
}
|
|
setItems(fetched.map((c) => ({ id: c.id, title: c.name })));
|
|
} catch (err) {
|
|
console.error("Failed to load conversations:", err);
|
|
}
|
|
};
|
|
load();
|
|
}, []);
|
|
|
|
// Keep in sync when parent updates conversations
|
|
useEffect(() => {
|
|
setItems(conversations);
|
|
}, [conversations]);
|
|
|
|
return (
|
|
<div className="flex flex-col gap-1">
|
|
{/* New thread button */}
|
|
<button
|
|
onClick={onCreateNewConversation}
|
|
className={cn(
|
|
"flex items-center gap-2 w-full px-3 py-2 rounded-xl",
|
|
"text-sm transition-all duration-150 cursor-pointer mb-1",
|
|
variant === "dark"
|
|
? "text-cream/60 hover:text-cream hover:bg-white/8"
|
|
: "text-warm-gray hover:text-charcoal hover:bg-cream-dark",
|
|
)}
|
|
>
|
|
<Plus size={14} strokeWidth={2.5} />
|
|
<span>New thread</span>
|
|
</button>
|
|
|
|
{/* Conversation items */}
|
|
{items.map((conv) => {
|
|
const isActive = conv.id === selectedId;
|
|
return (
|
|
<button
|
|
key={conv.id}
|
|
onClick={() => onSelectConversation(conv)}
|
|
className={cn(
|
|
"w-full px-3 py-2 rounded-xl text-left",
|
|
"text-sm truncate transition-all duration-150 cursor-pointer",
|
|
variant === "dark"
|
|
? isActive
|
|
? "bg-white/12 text-cream font-medium"
|
|
: "text-cream/60 hover:text-cream hover:bg-white/8"
|
|
: isActive
|
|
? "bg-cream-dark text-charcoal font-medium"
|
|
: "text-warm-gray hover:text-charcoal hover:bg-cream-dark",
|
|
)}
|
|
>
|
|
{conv.title}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
};
|