LibreChat/client/src/Providers/ActivePanelContext.tsx
Danny Avila 7b48203906
🗂️ feat: Sidebar Icon Toggle & New Chat History Switch (#12642)
* 🗂️ feat: Sidebar Icon Toggle & New Chat History Switch

Add collapse-on-active-click for sidebar icons (VSCode-style) and optionally switch to Chat History panel when creating a new chat.

* fix: Address review findings — extract DEFAULT_PANEL constant, add tests

Export DEFAULT_PANEL from ActivePanelContext and use it in ExpandedPanel
instead of hardcoding 'conversations'. Add ExpandedPanel tests covering
NavIconButton collapse toggle and NewChatButton panel switch behaviors.

* fix: Address review — prop-drill setActive, test disabled setting, strengthen assertions

Pass setActive as a prop to NewChatButton instead of subscribing to
ActivePanelContext, avoiding wasted re-renders on every panel switch.
Add negative-path test for switchToHistory=false. Add positive panel
assertions to inactive-icon click tests. Fix import order.
2026-04-13 09:46:38 -04:00

45 lines
1.4 KiB
TypeScript

import { createContext, useCallback, useContext, useMemo, useState, ReactNode } from 'react';
const STORAGE_KEY = 'side:active-panel';
export const DEFAULT_PANEL = 'conversations';
function getInitialActivePanel(): string {
const saved = localStorage.getItem(STORAGE_KEY);
return saved ? saved : DEFAULT_PANEL;
}
interface ActivePanelContextType {
active: string;
setActive: (id: string) => void;
}
const ActivePanelContext = createContext<ActivePanelContextType | undefined>(undefined);
export function ActivePanelProvider({ children }: { children: ReactNode }) {
const [active, _setActive] = useState<string>(getInitialActivePanel);
const setActive = useCallback((id: string) => {
localStorage.setItem(STORAGE_KEY, id);
_setActive(id);
}, []);
const value = useMemo(() => ({ active, setActive }), [active, setActive]);
return <ActivePanelContext.Provider value={value}>{children}</ActivePanelContext.Provider>;
}
export function useActivePanel() {
const context = useContext(ActivePanelContext);
if (context === undefined) {
throw new Error('useActivePanel must be used within an ActivePanelProvider');
}
return context;
}
/** Returns `active` when it matches a known link, otherwise the first link's id. */
export function resolveActivePanel(active: string, links: { id: string }[]): string {
if (links.length > 0 && links.some((l) => l.id === active)) {
return active;
}
return links[0]?.id ?? active;
}