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.
This commit is contained in:
webdevcody
2026-01-09 16:40:07 -05:00
parent 7ea64b32f3
commit 89a0877bcc
2 changed files with 43 additions and 10 deletions

View File

@@ -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 (
<div>
{/* Parent item - non-clickable label */}
<div
{/* Parent item - clickable to toggle dropdown */}
<button
onClick={() => 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'
)}
>
<Icon
className={cn(
'w-4 h-4 shrink-0 transition-all duration-200',
isParentActive || hasActiveSubItem ? 'text-brand-500' : 'text-muted-foreground'
isParentActive || hasActiveSubItem ? 'text-brand-500' : 'group-hover:text-brand-400'
)}
/>
<span className="truncate">{item.label}</span>
</div>
{/* Sub-items - always displayed */}
{item.subItems && (
<span className="truncate flex-1">{item.label}</span>
<ChevronIcon
className={cn(
'w-4 h-4 shrink-0 transition-transform duration-200',
'text-muted-foreground/60 group-hover:text-muted-foreground'
)}
/>
</button>
{/* Sub-items - conditionally displayed */}
{item.subItems && isOpen && (
<div className="ml-4 mt-1 space-y-1">
{item.subItems.map((subItem) => {
const SubIcon = subItem.icon;

View File

@@ -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();
}
}}
>
<Command>
<CommandInput placeholder="Search models..." />