Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export function SidebarFooter({
sidebarOpen ? 'justify-start' : 'justify-center',
'hover:scale-[1.02] active:scale-[0.97]'
)}
title={!sidebarOpen ? 'Settings' : undefined}
title={!sidebarOpen ? 'Global Settings' : undefined}
data-testid="settings-button"
>
<Settings
Expand All @@ -168,7 +168,7 @@ export function SidebarFooter({
sidebarOpen ? 'block' : 'hidden'
)}
>
Settings
Global Settings
</span>
{sidebarOpen && (
<span
Expand All @@ -194,7 +194,7 @@ export function SidebarFooter({
'translate-x-1 group-hover:translate-x-0'
)}
>
Settings
Global Settings
<span className="ml-2 px-1.5 py-0.5 bg-muted rounded text-[10px] font-mono text-muted-foreground">
{formatShortcut(shortcuts.settings, true)}
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ export function SidebarNavigation({
</span>
</div>
)}
{section.label && !sidebarOpen && <div className="h-px bg-border/30 mx-2 my-1.5"></div>}
{/* Separator for sections without label (visual separation) */}
{!section.label && sectionIdx > 0 && sidebarOpen && (
<div className="h-px bg-border/40 mx-3 mb-4"></div>
)}
{(section.label || sectionIdx > 0) && !sidebarOpen && (
<div className="h-px bg-border/30 mx-2 my-1.5"></div>
)}

{/* Nav Items */}
<div className="space-y-1.5">
Expand Down
19 changes: 17 additions & 2 deletions apps/ui/src/components/layout/sidebar/hooks/use-navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Lightbulb,
Brain,
Network,
Settings,
} from 'lucide-react';
import type { NavSection, NavItem } from '../types';
import type { KeyboardShortcut } from '@/hooks/use-keyboard-shortcuts';
Expand All @@ -32,6 +33,7 @@ interface UseNavigationProps {
agent: string;
terminal: string;
settings: string;
projectSettings: string;
ideation: string;
githubIssues: string;
githubPrs: string;
Expand Down Expand Up @@ -199,6 +201,19 @@ export function useNavigation({
});
}

// Add Project Settings as a standalone section (no label for visual separation)
sections.push({
label: '',
items: [
{
id: 'project-settings',
label: 'Project Settings',
icon: Settings,
shortcut: shortcuts.projectSettings,
},
],
});

return sections;
}, [
shortcuts,
Expand Down Expand Up @@ -257,11 +272,11 @@ export function useNavigation({
});
});

// Add settings shortcut
// Add global settings shortcut
shortcutsList.push({
key: shortcuts.settings,
action: () => navigate({ to: '/settings' }),
description: 'Navigate to Settings',
description: 'Navigate to Global Settings',
});
}

Expand Down
5 changes: 2 additions & 3 deletions apps/ui/src/components/ui/shell-syntax-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ const editorTheme = EditorView.theme({
backgroundColor: 'oklch(0.55 0.25 265 / 0.3)',
},
'.cm-activeLine': {
backgroundColor: 'var(--accent)',
opacity: '0.3',
backgroundColor: 'transparent',
},
'.cm-line': {
padding: '0 0.25rem',
Expand Down Expand Up @@ -114,7 +113,7 @@ export function ShellSyntaxEditor({
}: ShellSyntaxEditorProps) {
return (
<div
className={cn('w-full rounded-lg border border-border bg-muted/30', className)}
className={cn('w-full rounded-lg border border-border bg-background', className)}
style={{ minHeight }}
data-testid={testId}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { X } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { PROJECT_SETTINGS_NAV_ITEMS } from '../config/navigation';
import type { ProjectSettingsViewId } from '../hooks/use-project-settings-view';

interface ProjectSettingsNavigationProps {
activeSection: ProjectSettingsViewId;
onNavigate: (sectionId: ProjectSettingsViewId) => void;
isOpen?: boolean;
onClose?: () => void;
}

export function ProjectSettingsNavigation({
activeSection,
onNavigate,
isOpen = true,
onClose,
}: ProjectSettingsNavigationProps) {
return (
<>
{/* Mobile backdrop overlay - only shown when isOpen is true on mobile */}
{isOpen && (
<div
className="fixed inset-0 bg-black/50 z-20 lg:hidden"
onClick={onClose}
data-testid="project-settings-nav-backdrop"
/>
)}

{/* Navigation sidebar */}
<nav
className={cn(
// Mobile: fixed position overlay with slide transition
'fixed inset-y-0 left-0 w-72 z-30',
'transition-transform duration-200 ease-out',
// Hide on mobile when closed, show when open
isOpen ? 'translate-x-0' : '-translate-x-full',
// Desktop: relative position in layout, always visible
'lg:relative lg:w-64 lg:z-auto lg:translate-x-0',
'shrink-0 overflow-y-auto',
'border-r border-border/50',
'bg-gradient-to-b from-card/95 via-card/90 to-card/85 backdrop-blur-xl',
// Desktop background
'lg:from-card/80 lg:via-card/60 lg:to-card/40'
)}
>
{/* Mobile close button */}
<div className="lg:hidden flex items-center justify-between px-4 py-3 border-b border-border/50">
<span className="text-sm font-semibold text-foreground">Navigation</span>
<Button
variant="ghost"
size="sm"
onClick={onClose}
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
aria-label="Close navigation menu"
>
<X className="w-4 h-4" />
</Button>
</div>

<div className="sticky top-0 p-4 space-y-1">
{PROJECT_SETTINGS_NAV_ITEMS.map((item) => {
const Icon = item.icon;
const isActive = activeSection === item.id;
const isDanger = item.id === 'danger';

return (
<button
key={item.id}
onClick={() => onNavigate(item.id)}
className={cn(
'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 relative overflow-hidden',
isActive
? [
isDanger
? 'bg-gradient-to-r from-red-500/15 via-red-500/10 to-red-600/5'
: 'bg-gradient-to-r from-brand-500/15 via-brand-500/10 to-brand-600/5',
'text-foreground',
isDanger ? 'border border-red-500/25' : 'border border-brand-500/25',
isDanger ? 'shadow-sm shadow-red-500/5' : 'shadow-sm shadow-brand-500/5',
]
: [
'text-muted-foreground hover:text-foreground',
'hover:bg-accent/50',
'border border-transparent hover:border-border/40',
],
'hover:scale-[1.01] active:scale-[0.98]'
)}
>
{/* Active indicator bar */}
{isActive && (
<div
className={cn(
'absolute inset-y-0 left-0 w-0.5 rounded-r-full',
isDanger
? 'bg-gradient-to-b from-red-400 via-red-500 to-red-600'
: 'bg-gradient-to-b from-brand-400 via-brand-500 to-brand-600'
)}
/>
)}
<Icon
className={cn(
'w-4 h-4 shrink-0 transition-all duration-200',
isActive
? isDanger
? 'text-red-500'
: 'text-brand-500'
: isDanger
? 'group-hover:text-red-400 group-hover:scale-110'
: 'group-hover:text-brand-400 group-hover:scale-110'
)}
/>
<span className={cn(isDanger && !isActive && 'text-red-400/70')}>{item.label}</span>
</button>
);
})}
</div>
</nav>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { LucideIcon } from 'lucide-react';
import { User, GitBranch, Palette, AlertTriangle } from 'lucide-react';
import type { ProjectSettingsViewId } from '../hooks/use-project-settings-view';

export interface ProjectNavigationItem {
id: ProjectSettingsViewId;
label: string;
icon: LucideIcon;
}

export const PROJECT_SETTINGS_NAV_ITEMS: ProjectNavigationItem[] = [
{ id: 'identity', label: 'Identity', icon: User },
{ id: 'worktrees', label: 'Worktrees', icon: GitBranch },
{ id: 'theme', label: 'Theme', icon: Palette },
{ id: 'danger', label: 'Danger Zone', icon: AlertTriangle },
];
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useProjectSettingsView, type ProjectSettingsViewId } from './use-project-settings-view';
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useState, useCallback } from 'react';

export type ProjectSettingsViewId = 'identity' | 'theme' | 'worktrees' | 'danger';

interface UseProjectSettingsViewOptions {
initialView?: ProjectSettingsViewId;
}

export function useProjectSettingsView({
initialView = 'identity',
}: UseProjectSettingsViewOptions = {}) {
const [activeView, setActiveView] = useState<ProjectSettingsViewId>(initialView);

const navigateTo = useCallback((viewId: ProjectSettingsViewId) => {
setActiveView(viewId);
}, []);

return {
activeView,
navigateTo,
};
}
6 changes: 6 additions & 0 deletions apps/ui/src/components/views/project-settings-view/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { ProjectSettingsView } from './project-settings-view';
export { ProjectIdentitySection } from './project-identity-section';
export { ProjectThemeSection } from './project-theme-section';
export { WorktreePreferencesSection } from './worktree-preferences-section';
export { useProjectSettingsView, type ProjectSettingsViewId } from './hooks';
export { ProjectSettingsNavigation } from './components/project-settings-navigation';
Loading
Loading