From 89a0877bcca691ea46989bfd8589b87463cd699a Mon Sep 17 00:00:00 2001 From: webdevcody Date: Fri, 9 Jan 2026 16:40:07 -0500 Subject: [PATCH] feat: enhance settings navigation with collapsible sub-items and local storage state - Implemented a collapsible dropdown for navigation items in the settings view, allowing users to expand or collapse sub-items. - Added local storage functionality to remember the open/closed state of the dropdown across sessions. - Updated the styling and interaction logic for improved user experience and accessibility. These changes improve the organization of navigation items and enhance user interaction within the settings view. --- .../components/settings-navigation.tsx | 45 +++++++++++++++---- .../model-defaults/phase-model-selector.tsx | 8 +++- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/apps/ui/src/components/views/settings-view/components/settings-navigation.tsx b/apps/ui/src/components/views/settings-view/components/settings-navigation.tsx index fd3b4f07..7a4001c9 100644 --- a/apps/ui/src/components/views/settings-view/components/settings-navigation.tsx +++ b/apps/ui/src/components/views/settings-view/components/settings-navigation.tsx @@ -1,9 +1,13 @@ +import { useState, useEffect } from 'react'; +import { ChevronDown, ChevronRight } from 'lucide-react'; import { cn } from '@/lib/utils'; import type { Project } from '@/lib/electron'; import type { NavigationItem } from '../config/navigation'; import { GLOBAL_NAV_ITEMS, PROJECT_NAV_ITEMS } from '../config/navigation'; import type { SettingsViewId } from '../hooks/use-settings-view'; +const PROVIDERS_DROPDOWN_KEY = 'settings-providers-dropdown-open'; + interface SettingsNavigationProps { navItems: NavigationItem[]; activeSection: SettingsViewId; @@ -66,29 +70,52 @@ function NavItemWithSubItems({ activeSection: SettingsViewId; onNavigate: (sectionId: SettingsViewId) => void; }) { + const [isOpen, setIsOpen] = useState(() => { + if (typeof window !== 'undefined') { + const stored = localStorage.getItem(PROVIDERS_DROPDOWN_KEY); + return stored === null ? true : stored === 'true'; + } + return true; + }); + + useEffect(() => { + localStorage.setItem(PROVIDERS_DROPDOWN_KEY, String(isOpen)); + }, [isOpen]); + const hasActiveSubItem = item.subItems?.some((subItem) => subItem.id === activeSection) ?? false; const isParentActive = item.id === activeSection; const Icon = item.icon; + const ChevronIcon = isOpen ? ChevronDown : ChevronRight; return (
- {/* Parent item - non-clickable label */} -
setIsOpen(!isOpen)} className={cn( - 'w-full flex items-center gap-2.5 px-3 py-2.5 rounded-xl text-sm font-medium text-muted-foreground', - isParentActive || (hasActiveSubItem && 'text-foreground') + 'group w-full flex items-center gap-2.5 px-3 py-2.5 rounded-xl text-sm font-medium transition-all duration-200 ease-out text-left', + 'text-muted-foreground hover:text-foreground', + 'hover:bg-accent/50', + 'border border-transparent hover:border-border/40', + (isParentActive || hasActiveSubItem) && 'text-foreground' )} > - {item.label} -
- {/* Sub-items - always displayed */} - {item.subItems && ( + {item.label} + + + {/* Sub-items - conditionally displayed */} + {item.subItems && isOpen && (
{item.subItems.map((subItem) => { const SubIcon = subItem.icon; diff --git a/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx b/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx index 89387530..c64b5bd4 100644 --- a/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx +++ b/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx @@ -876,7 +876,13 @@ export function PhaseModelSelector({ className="w-[320px] p-0" align={align} onWheel={(e) => e.stopPropagation()} - onPointerDownOutside={(e) => e.preventDefault()} + onPointerDownOutside={(e) => { + // Only prevent close if clicking inside a nested popover (thinking level panel) + const target = e.target as HTMLElement; + if (target.closest('[data-slot="popover-content"]')) { + e.preventDefault(); + } + }} >