- ChatPanelContext with localStorage persistence (nexus:chat-panel-open) - ChatInput with Enter/Shift+Enter/Escape keyboard shortcuts and auto-resize - ChatMessage renders user bubbles (bg-secondary) and assistant markdown via ChatMarkdownMessage - ChatInput.test.tsx with 6 passing tests (keyboard shortcuts, max-height, submit state)
62 lines
1.6 KiB
TypeScript
62 lines
1.6 KiB
TypeScript
import { createContext, useCallback, useContext, useState, type ReactNode } from "react";
|
|
|
|
const STORAGE_KEY = "nexus:chat-panel-open";
|
|
|
|
interface ChatPanelContextValue {
|
|
chatOpen: boolean;
|
|
activeConversationId: string | null;
|
|
setChatOpen: (open: boolean) => void;
|
|
toggleChat: () => void;
|
|
setActiveConversationId: (id: string | null) => void;
|
|
}
|
|
|
|
const ChatPanelContext = createContext<ChatPanelContextValue | null>(null);
|
|
|
|
function readPreference(): boolean {
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
return raw === "true";
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function writePreference(open: boolean) {
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, String(open));
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
|
|
export function ChatPanelProvider({ children }: { children: ReactNode }) {
|
|
const [chatOpen, setChatOpenState] = useState(readPreference);
|
|
const [activeConversationId, setActiveConversationId] = useState<string | null>(null);
|
|
|
|
const setChatOpen = useCallback((open: boolean) => {
|
|
setChatOpenState(open);
|
|
writePreference(open);
|
|
}, []);
|
|
|
|
const toggleChat = useCallback(() => {
|
|
setChatOpenState((prev) => {
|
|
const next = !prev;
|
|
writePreference(next);
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
return (
|
|
<ChatPanelContext.Provider
|
|
value={{ chatOpen, activeConversationId, setChatOpen, toggleChat, setActiveConversationId }}
|
|
>
|
|
{children}
|
|
</ChatPanelContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useChatPanel() {
|
|
const ctx = useContext(ChatPanelContext);
|
|
if (!ctx) throw new Error("useChatPanel must be used within ChatPanelProvider");
|
|
return ctx;
|
|
}
|