mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
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:
@@ -1,9 +1,13 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import type { Project } from '@/lib/electron';
|
import type { Project } from '@/lib/electron';
|
||||||
import type { NavigationItem } from '../config/navigation';
|
import type { NavigationItem } from '../config/navigation';
|
||||||
import { GLOBAL_NAV_ITEMS, PROJECT_NAV_ITEMS } from '../config/navigation';
|
import { GLOBAL_NAV_ITEMS, PROJECT_NAV_ITEMS } from '../config/navigation';
|
||||||
import type { SettingsViewId } from '../hooks/use-settings-view';
|
import type { SettingsViewId } from '../hooks/use-settings-view';
|
||||||
|
|
||||||
|
const PROVIDERS_DROPDOWN_KEY = 'settings-providers-dropdown-open';
|
||||||
|
|
||||||
interface SettingsNavigationProps {
|
interface SettingsNavigationProps {
|
||||||
navItems: NavigationItem[];
|
navItems: NavigationItem[];
|
||||||
activeSection: SettingsViewId;
|
activeSection: SettingsViewId;
|
||||||
@@ -66,29 +70,52 @@ function NavItemWithSubItems({
|
|||||||
activeSection: SettingsViewId;
|
activeSection: SettingsViewId;
|
||||||
onNavigate: (sectionId: SettingsViewId) => void;
|
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 hasActiveSubItem = item.subItems?.some((subItem) => subItem.id === activeSection) ?? false;
|
||||||
const isParentActive = item.id === activeSection;
|
const isParentActive = item.id === activeSection;
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
|
const ChevronIcon = isOpen ? ChevronDown : ChevronRight;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* Parent item - non-clickable label */}
|
{/* Parent item - clickable to toggle dropdown */}
|
||||||
<div
|
<button
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full flex items-center gap-2.5 px-3 py-2.5 rounded-xl text-sm font-medium text-muted-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',
|
||||||
isParentActive || (hasActiveSubItem && 'text-foreground')
|
'text-muted-foreground hover:text-foreground',
|
||||||
|
'hover:bg-accent/50',
|
||||||
|
'border border-transparent hover:border-border/40',
|
||||||
|
(isParentActive || hasActiveSubItem) && 'text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-4 h-4 shrink-0 transition-all duration-200',
|
'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>
|
<span className="truncate flex-1">{item.label}</span>
|
||||||
</div>
|
<ChevronIcon
|
||||||
{/* Sub-items - always displayed */}
|
className={cn(
|
||||||
{item.subItems && (
|
'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">
|
<div className="ml-4 mt-1 space-y-1">
|
||||||
{item.subItems.map((subItem) => {
|
{item.subItems.map((subItem) => {
|
||||||
const SubIcon = subItem.icon;
|
const SubIcon = subItem.icon;
|
||||||
|
|||||||
@@ -876,7 +876,13 @@ export function PhaseModelSelector({
|
|||||||
className="w-[320px] p-0"
|
className="w-[320px] p-0"
|
||||||
align={align}
|
align={align}
|
||||||
onWheel={(e) => e.stopPropagation()}
|
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>
|
<Command>
|
||||||
<CommandInput placeholder="Search models..." />
|
<CommandInput placeholder="Search models..." />
|
||||||
|
|||||||
Reference in New Issue
Block a user