feat(nav-rail): add expand/collapse toggle with project names and settings control (#511)
* feat(nav-rail): add expand/collapse toggle with project names and settings control The NavRail introduced in v1.7.5 only shows project icons without names, making it difficult to identify which project is active when multiple projects share similar icons or use letter avatars. Changes: - Add expandable NavRail that shows full project names alongside icons - Default to expanded when multiple projects are open - Auto-collapse to icon-only mode with a single project (nothing to differentiate) - Add toggle button at bottom of rail with Cmd+Shift+E keyboard shortcut - Highlight active project with interactive.selection background for clarity - Show keyboard shortcut hints in tooltip and expanded label - Add 'Expand project rail' checkbox in Settings > Appearance > Navigation - Persist expansion state across sessions Fixes: NavRail project icons are ambiguous without visible project names * feat: improve nav rail toggle and navigation settings - Make nav rail collapsed by default with smoother text fade behavior - Move nav rail and terminal quick keys controls into a dedicated Navigation section - Update keyboard shortcuts for logs/status and nav rail expand/collapse --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
a59bf010bb
commit
9a2af4c4d9
@@ -97,9 +97,9 @@ const ShortcutsSectionContent: React.FC = () => {
|
||||
return <KeyboardShortcutsSettings />;
|
||||
};
|
||||
|
||||
// Visual section: Theme Mode, Font Size, Spacing, Corner Radius, Input Bar Offset (mobile)
|
||||
// Visual section: Theme Mode, Font Size, Spacing, Corner Radius, Input Bar Offset (mobile), Nav Rail
|
||||
const VisualSectionContent: React.FC = () => {
|
||||
return <OpenChamberVisualSettings visibleSettings={['theme', 'fontSize', 'terminalFontSize', 'spacing', 'cornerRadius', 'inputBarOffset', 'terminalQuickKeys']} />;
|
||||
return <OpenChamberVisualSettings visibleSettings={['theme', 'fontSize', 'terminalFontSize', 'spacing', 'cornerRadius', 'inputBarOffset', 'terminalQuickKeys', 'navRail']} />;
|
||||
};
|
||||
|
||||
// Chat section: Default Tool Output, Diff layout, Mobile status bar, Show reasoning traces, Queue mode, Persist draft
|
||||
|
||||
@@ -83,7 +83,7 @@ const DIFF_VIEW_MODE_OPTIONS: Option<'single' | 'stacked'>[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export type VisibleSetting = 'theme' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'cornerRadius' | 'inputBarOffset' | 'toolOutput' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'reasoning' | 'queueMode' | 'textJustificationActivity' | 'terminalQuickKeys' | 'persistDraft';
|
||||
export type VisibleSetting = 'theme' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'cornerRadius' | 'inputBarOffset' | 'navRail' | 'toolOutput' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'reasoning' | 'queueMode' | 'textJustificationActivity' | 'terminalQuickKeys' | 'persistDraft';
|
||||
|
||||
interface OpenChamberVisualSettingsProps {
|
||||
/** Which settings to show. If undefined, shows all. */
|
||||
@@ -119,6 +119,8 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
const setQueueMode = useMessageQueueStore(state => state.setQueueMode);
|
||||
const persistChatDraft = useUIStore(state => state.persistChatDraft);
|
||||
const setPersistChatDraft = useUIStore(state => state.setPersistChatDraft);
|
||||
const isNavRailExpanded = useUIStore(state => state.isNavRailExpanded);
|
||||
const setNavRailExpanded = useUIStore(state => state.setNavRailExpanded);
|
||||
const showMobileSessionStatusBar = useUIStore(state => state.showMobileSessionStatusBar);
|
||||
const setShowMobileSessionStatusBar = useUIStore(state => state.setShowMobileSessionStatusBar);
|
||||
const {
|
||||
@@ -170,6 +172,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
|
||||
const hasAppearanceSettings = shouldShow('theme') && !isVSCodeRuntime();
|
||||
const hasLayoutSettings = shouldShow('fontSize') || shouldShow('terminalFontSize') || shouldShow('spacing') || shouldShow('cornerRadius') || shouldShow('inputBarOffset');
|
||||
const hasNavigationSettings = (!isMobile && shouldShow('navRail')) || (shouldShow('terminalQuickKeys') && !isMobile);
|
||||
const hasBehaviorSettings = shouldShow('toolOutput')
|
||||
|| shouldShow('diffLayout')
|
||||
|| (shouldShow('mobileStatusBar') && isMobile)
|
||||
@@ -177,8 +180,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
|| shouldShow('reasoning')
|
||||
|| shouldShow('queueMode')
|
||||
|| shouldShow('textJustificationActivity')
|
||||
|| shouldShow('persistDraft')
|
||||
|| (shouldShow('terminalQuickKeys') && !isMobile);
|
||||
|| shouldShow('persistDraft');
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
|
||||
@@ -244,24 +246,28 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 py-1.5">
|
||||
<ButtonSmall
|
||||
<button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="xs"
|
||||
disabled={customThemesLoading || themesReloading}
|
||||
onClick={async () => {
|
||||
const startedAt = Date.now();
|
||||
setThemesReloading(true);
|
||||
try {
|
||||
await reloadCustomThemes();
|
||||
} finally {
|
||||
const elapsed = Date.now() - startedAt;
|
||||
if (elapsed < 500) {
|
||||
await new Promise<void>((resolve) => {
|
||||
window.setTimeout(resolve, 500 - elapsed);
|
||||
});
|
||||
}
|
||||
setThemesReloading(false);
|
||||
}
|
||||
}}
|
||||
className="!font-normal"
|
||||
className="inline-flex items-center typography-ui-label font-normal text-foreground underline decoration-[1px] underline-offset-2 hover:text-foreground/80 disabled:cursor-not-allowed disabled:text-muted-foreground/60"
|
||||
>
|
||||
<RiRestartLine className={cn('h-3.5 w-3.5', themesReloading && 'animate-spin')} />
|
||||
Reload themes
|
||||
</ButtonSmall>
|
||||
{themesReloading ? 'Reloading themes...' : 'Reload themes'}
|
||||
</button>
|
||||
<Tooltip delayDuration={700}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@@ -285,9 +291,11 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
{hasLayoutSettings && (
|
||||
<div className="mb-8 space-y-3">
|
||||
<section className="p-2 space-y-0.5">
|
||||
<h4 className="typography-ui-header font-medium text-foreground">Spacing & Layout</h4>
|
||||
<div className="pl-2">
|
||||
|
||||
{shouldShow('fontSize') && !isMobile && (
|
||||
<div className="flex items-center gap-8 py-1.5">
|
||||
<div className="flex items-center gap-8 py-1">
|
||||
<div className="flex min-w-0 flex-col w-56 shrink-0">
|
||||
<span className="typography-ui-label text-foreground">Interface Font Size</span>
|
||||
</div>
|
||||
@@ -317,7 +325,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
)}
|
||||
|
||||
{shouldShow('terminalFontSize') && (
|
||||
<div className={cn("py-1.5", isMobile ? "flex flex-col gap-3" : "flex items-center gap-8")}>
|
||||
<div className={cn("py-1", isMobile ? "flex flex-col gap-3" : "flex items-center gap-8")}>
|
||||
<div className={cn("flex min-w-0 flex-col", isMobile ? "w-full" : "w-56 shrink-0")}>
|
||||
<span className="typography-ui-label text-foreground">Terminal Font Size</span>
|
||||
</div>
|
||||
@@ -346,7 +354,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
)}
|
||||
|
||||
{shouldShow('spacing') && (
|
||||
<div className={cn("py-1.5", isMobile ? "flex flex-col gap-3" : "flex items-center gap-8")}>
|
||||
<div className={cn("py-1", isMobile ? "flex flex-col gap-3" : "flex items-center gap-8")}>
|
||||
<div className={cn("flex min-w-0 flex-col", isMobile ? "w-full" : "w-56 shrink-0")}>
|
||||
<span className="typography-ui-label text-foreground">Spacing Density</span>
|
||||
</div>
|
||||
@@ -375,7 +383,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
)}
|
||||
|
||||
{shouldShow('cornerRadius') && (
|
||||
<div className={cn("py-1.5", isMobile ? "flex flex-col gap-3" : "flex items-center gap-8")}>
|
||||
<div className={cn("py-1", isMobile ? "flex flex-col gap-3" : "flex items-center gap-8")}>
|
||||
<div className={cn("flex min-w-0 flex-col", isMobile ? "w-full" : "w-56 shrink-0")}>
|
||||
<span className="typography-ui-label text-foreground">Corner Radius</span>
|
||||
</div>
|
||||
@@ -404,7 +412,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
)}
|
||||
|
||||
{shouldShow('inputBarOffset') && (
|
||||
<div className={cn("py-1.5", isMobile ? "flex flex-col gap-3" : "flex items-center gap-8")}>
|
||||
<div className={cn("py-1", isMobile ? "flex flex-col gap-3" : "flex items-center gap-8")}>
|
||||
<div className={cn("flex min-w-0 flex-col", isMobile ? "w-full" : "w-56 shrink-0")}>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="typography-ui-label text-foreground">Input Bar Offset</span>
|
||||
@@ -442,6 +450,81 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* --- Navigation --- */}
|
||||
{hasNavigationSettings && (
|
||||
<div className="space-y-3">
|
||||
<section className="px-2 pb-2 pt-0">
|
||||
<h4 className="typography-ui-header font-medium text-foreground">Navigation</h4>
|
||||
{shouldShow('navRail') && !isMobile && (
|
||||
<div
|
||||
className="group mt-1.5 flex cursor-pointer items-center gap-2 py-1.5"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => setNavRailExpanded(!isNavRailExpanded)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
setNavRailExpanded(!isNavRailExpanded);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isNavRailExpanded}
|
||||
onChange={setNavRailExpanded}
|
||||
ariaLabel="Expand project rail by default"
|
||||
/>
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="typography-ui-label text-foreground">Expand project rail</span>
|
||||
<Tooltip delayDuration={1000}>
|
||||
<TooltipTrigger asChild>
|
||||
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent sideOffset={8} className="max-w-xs">
|
||||
Show project names in the left rail when multiple projects are open. Auto-collapses with a single project.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shouldShow('terminalQuickKeys') && !isMobile && (
|
||||
<div
|
||||
className="group flex cursor-pointer items-center gap-2 py-1.5"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-pressed={showTerminalQuickKeysOnDesktop}
|
||||
onClick={() => setShowTerminalQuickKeysOnDesktop(!showTerminalQuickKeysOnDesktop)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
setShowTerminalQuickKeysOnDesktop(!showTerminalQuickKeysOnDesktop);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={showTerminalQuickKeysOnDesktop}
|
||||
onChange={setShowTerminalQuickKeysOnDesktop}
|
||||
ariaLabel="Terminal quick keys"
|
||||
/>
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="typography-ui-label text-foreground">Terminal Quick Keys</span>
|
||||
<Tooltip delayDuration={1000}>
|
||||
<TooltipTrigger asChild>
|
||||
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent sideOffset={8} className="max-w-xs">
|
||||
Show Esc, Ctrl, Arrows in terminal view
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
@@ -699,40 +782,6 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{shouldShow('terminalQuickKeys') && !isMobile && (
|
||||
<section className="p-2">
|
||||
<div
|
||||
className="group flex cursor-pointer items-center gap-2 rounded-md py-1.5 transition-colors hover:bg-[var(--interactive-hover)]/30"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-pressed={showTerminalQuickKeysOnDesktop}
|
||||
onClick={() => setShowTerminalQuickKeysOnDesktop(!showTerminalQuickKeysOnDesktop)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
setShowTerminalQuickKeysOnDesktop(!showTerminalQuickKeysOnDesktop);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={showTerminalQuickKeysOnDesktop}
|
||||
onChange={setShowTerminalQuickKeysOnDesktop}
|
||||
ariaLabel="Terminal quick keys"
|
||||
/>
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="typography-ui-label text-foreground">Terminal Quick Keys</span>
|
||||
<Tooltip delayDuration={1000}>
|
||||
<TooltipTrigger asChild>
|
||||
<RiInformationLine className="h-3.5 w-3.5 text-muted-foreground/60 cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent sideOffset={8} className="max-w-xs">
|
||||
Show Esc, Ctrl, Arrows in terminal view
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user