fix(ui): keep settings sidebar open, center content, and fix worktree refresh (#1058)
* fix(ui): keep settings sidebar open, center content, and fix worktree refresh - Prevent settings nav from collapsing on split pages (Magic Prompts, Agents, etc.) - Center settings content with max-w-3xl for better layout on wide screens - Widen settings window from 960px to 1200px max-width - Fix source branch dropdown overflow with max-height and scrolling - Add worktreeRefreshNonce to trigger immediate sidebar refresh after worktree creation * ref(ui): address Greptile review — remove dead isNavCollapsed code and split worktree effect - Remove isNavCollapsed variable and all dead branches (resize handle, renderSettingsNav param, SETTINGS_NAV_RAIL_WIDTH) - Split SessionSidebar effect so worktreeRefreshNonce only triggers discoverWorktrees, not refreshGlobalSessions - Both type-check and lint pass * fix(ui): refine settings and worktree dropdown behavior --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
4f51abddf4
commit
6ab363f8df
@@ -1593,7 +1593,7 @@ export function NewWorktreeDialog({
|
||||
<RiArrowDownSLine className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" sideOffset={6} className="w-[320px] p-0 max-h-[min(var(--available-height),24rem)] flex flex-col overflow-hidden" ref={existingBranchDropdownContentRef}>
|
||||
<DropdownMenuContent align="start" sideOffset={6} portalToBody className="w-[min(42rem,calc(100vw-2rem))] p-0 max-h-[min(var(--available-height),24rem)] flex flex-col overflow-hidden" ref={existingBranchDropdownContentRef}>
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder={t('session.newWorktree.searchBranches')}
|
||||
@@ -1833,7 +1833,7 @@ export function NewWorktreeDialog({
|
||||
<RiArrowDownSLine className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-[320px] p-0" ref={sourceBranchDropdownContentRef}>
|
||||
<DropdownMenuContent align="start" portalToBody className="w-[min(42rem,calc(100vw-2rem))] p-0 max-h-[min(var(--available-height),24rem)] flex flex-col overflow-hidden" ref={sourceBranchDropdownContentRef}>
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder={t('session.newWorktree.searchBranches')}
|
||||
|
||||
@@ -93,6 +93,7 @@ type ContentProps = {
|
||||
align?: "start" | "center" | "end";
|
||||
side?: "top" | "right" | "bottom" | "left";
|
||||
alignOffset?: number;
|
||||
portalToBody?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
@@ -105,6 +106,7 @@ function DropdownMenuContent({
|
||||
align,
|
||||
side,
|
||||
alignOffset,
|
||||
portalToBody = false,
|
||||
style,
|
||||
children,
|
||||
onCloseAutoFocus,
|
||||
@@ -114,7 +116,7 @@ function DropdownMenuContent({
|
||||
void onCloseAutoFocus
|
||||
|
||||
return (
|
||||
<BaseMenu.Portal container={portalContext?.portalContainer || undefined}>
|
||||
<BaseMenu.Portal container={portalToBody ? undefined : portalContext?.portalContainer || undefined}>
|
||||
<BaseMenu.Positioner
|
||||
sideOffset={sideOffset}
|
||||
align={align}
|
||||
|
||||
@@ -71,7 +71,6 @@ import {
|
||||
// Same constraints as main sidebar
|
||||
const SETTINGS_NAV_MIN_WIDTH = 176;
|
||||
const SETTINGS_NAV_MAX_WIDTH = 280;
|
||||
const SETTINGS_NAV_RAIL_WIDTH = 48;
|
||||
|
||||
type MobileStage = 'nav' | 'page-sidebar' | 'page-content';
|
||||
|
||||
@@ -372,8 +371,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
||||
return getSettingsPageMeta(settingsSlug);
|
||||
}, [settingsSlug]);
|
||||
|
||||
// Collapse main nav to icon rail when active page has its own sidebar
|
||||
const isNavCollapsed = !isMobile && activePageMeta?.kind === 'split';
|
||||
// Nav is always open (collapsed state removed)
|
||||
|
||||
const openChamberSectionBySlug: Partial<Record<SettingsPageSlug, OpenChamberSection>> = React.useMemo(() => ({
|
||||
appearance: 'visual',
|
||||
@@ -544,7 +542,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
||||
setMobileStage('page-sidebar');
|
||||
}, []);
|
||||
|
||||
const renderSettingsNav = (collapsed: boolean) => {
|
||||
const renderSettingsNav = () => {
|
||||
return (
|
||||
<div className="flex h-full flex-col overflow-hidden">
|
||||
{/* Scrollable nav items */}
|
||||
@@ -556,7 +554,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
||||
if (!Icon) return null;
|
||||
|
||||
return (
|
||||
<Tooltip key={page.slug} delayDuration={collapsed ? 100 : 600}>
|
||||
<Tooltip key={page.slug} delayDuration={600}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
@@ -569,12 +567,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
||||
)}
|
||||
>
|
||||
<Icon className="h-4 w-4 shrink-0" />
|
||||
<span
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 whitespace-nowrap overflow-hidden transition-opacity duration-150',
|
||||
collapsed ? 'opacity-0' : 'opacity-100'
|
||||
)}
|
||||
>
|
||||
<span className="flex items-center gap-1.5 whitespace-nowrap overflow-hidden transition-opacity duration-150 opacity-100">
|
||||
<span className="typography-ui-label font-normal truncate">{getPageTitle(page.slug)}</span>
|
||||
{(page.slug === 'voice' || page.slug === 'tunnel') && (
|
||||
<span className="shrink-0 typography-micro px-1 rounded leading-none pb-px text-[var(--status-warning)] bg-[var(--status-warning)]/10">
|
||||
@@ -584,24 +577,14 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
||||
</span>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
{collapsed && (
|
||||
<TooltipContent side="right" sideOffset={8}>
|
||||
{getPageTitle(page.slug)}
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer — hidden when collapsed via overflow on parent */}
|
||||
<div
|
||||
className={cn(
|
||||
'overflow-hidden transition-opacity duration-150',
|
||||
collapsed ? 'opacity-0 pointer-events-none' : 'opacity-100'
|
||||
)}
|
||||
>
|
||||
{/* Footer */}
|
||||
<div className="overflow-hidden transition-opacity duration-150 opacity-100">
|
||||
<div className="border-t border-border bg-sidebar px-2 py-1 space-y-0.5">
|
||||
{!runtimeCtx.isVSCode && (
|
||||
<Tooltip delayDuration={300}>
|
||||
@@ -636,7 +619,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
||||
return (
|
||||
<div className={cn('flex-1 min-h-0 overflow-hidden', runtimeCtx.isVSCode ? 'bg-background' : 'bg-sidebar')}>
|
||||
<div className="flex h-full min-h-0 flex-col">
|
||||
<ErrorBoundary>{renderSettingsNav(false)}</ErrorBoundary>
|
||||
<ErrorBoundary>{renderSettingsNav()}</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -686,16 +669,20 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
||||
<div className={cn('w-[264px] min-w-[264px] border-r', runtimeCtx.isVSCode ? 'bg-background' : 'bg-sidebar')} style={{ borderColor: 'var(--interactive-border)' }}>
|
||||
<ErrorBoundary>{renderPageSidebar(settingsSlug, {})}</ErrorBoundary>
|
||||
</div>
|
||||
<div className="flex-1 overflow-hidden bg-background">
|
||||
<ErrorBoundary>{renderPageContent(settingsSlug)}</ErrorBoundary>
|
||||
<div className="flex-1 overflow-auto scrollbar-none bg-background">
|
||||
<div className="mx-auto w-full max-w-3xl">
|
||||
<ErrorBoundary>{renderPageContent(settingsSlug)}</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-hidden bg-background">
|
||||
<ErrorBoundary>{renderPageContent(settingsSlug)}</ErrorBoundary>
|
||||
<div className="h-full overflow-auto scrollbar-none bg-background">
|
||||
<div className="mx-auto w-full max-w-3xl">
|
||||
<ErrorBoundary>{renderPageContent(settingsSlug)}</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -792,28 +779,26 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
||||
: runtimeCtx.isVSCode
|
||||
? 'bg-background'
|
||||
: 'bg-sidebar',
|
||||
isResizing && !isNavCollapsed ? '' : 'transition-[width,min-width] duration-200 ease-[cubic-bezier(0.25,0.1,0.25,1)]'
|
||||
isResizing ? '' : 'transition-[width,min-width] duration-200 ease-[cubic-bezier(0.25,0.1,0.25,1)]'
|
||||
)}
|
||||
style={{
|
||||
width: isNavCollapsed ? `${SETTINGS_NAV_RAIL_WIDTH}px` : `${navWidth}px`,
|
||||
minWidth: isNavCollapsed ? `${SETTINGS_NAV_RAIL_WIDTH}px` : `${navWidth}px`,
|
||||
width: `${navWidth}px`,
|
||||
minWidth: `${navWidth}px`,
|
||||
borderColor: 'var(--interactive-border)',
|
||||
}}
|
||||
>
|
||||
{!isNavCollapsed && (
|
||||
<div
|
||||
className={cn(
|
||||
'absolute right-0 top-0 z-20 h-full w-[6px] -mr-[3px] cursor-col-resize',
|
||||
isResizing ? 'bg-primary/30' : 'bg-transparent hover:bg-primary/20'
|
||||
)}
|
||||
onPointerDown={handlePointerDown}
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label={t('settings.view.actions.resizeNavigation')}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute right-0 top-0 z-20 h-full w-[6px] -mr-[3px] cursor-col-resize',
|
||||
isResizing ? 'bg-primary/30' : 'bg-transparent hover:bg-primary/20'
|
||||
)}
|
||||
onPointerDown={handlePointerDown}
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label={t('settings.view.actions.resizeNavigation')}
|
||||
/>
|
||||
<ErrorBoundary>
|
||||
{renderSettingsNav(isNavCollapsed)}
|
||||
{renderSettingsNav()}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export const SettingsWindow: React.FC<SettingsWindowProps> = ({ open, onOpenChan
|
||||
aria-describedby={descriptionId}
|
||||
className={cn(
|
||||
'fixed z-50 top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]',
|
||||
'w-[90vw] max-w-[960px] h-[85vh] max-h-[900px]',
|
||||
'w-[90vw] max-w-[1200px] h-[85vh] max-h-[900px]',
|
||||
'rounded-xl border shadow-none overflow-hidden',
|
||||
'bg-background'
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user