From c0ebfc3ef2d000ee5dd25252e0e58c0f684bc3cd Mon Sep 17 00:00:00 2001 From: Gagancreates Date: Mon, 8 Jun 2026 01:57:40 +0530 Subject: [PATCH] feat: overflow menu for toolbar items that don't fit even as icons When the bar is too narrow to show every control as an icon, the right-most items move into a '...' overflow dropdown (code -> perm -> search -> workdir) instead of being clipped, so no icon is ever hidden. Toggle items keep the menu open on click via onSelect preventDefault. --- .../components/chat-input-with-mentions.tsx | 98 ++++++++++++++++--- 1 file changed, 86 insertions(+), 12 deletions(-) diff --git a/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx b/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx index 42b56e44..dfdfa335 100644 --- a/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx +++ b/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx @@ -19,6 +19,7 @@ import { ImagePlus, LoaderIcon, Mic, + MoreHorizontal, Plus, ShieldCheck, Square, @@ -29,10 +30,13 @@ import { import { Button } from '@/components/ui/button' import { DropdownMenu, + DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, + DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, + DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, @@ -283,10 +287,12 @@ function ChatInputInner({ const [permissionMode, setPermissionMode] = useState('auto') const [recentWorkDirs, setRecentWorkDirs] = useState([]) - // Responsive toolbar: measure real overflow and collapse items to icons - // right→left (1=code, 2=perm, 3=search-label, 4=workDir) until everything fits. - // overflow-hidden on the left group is the hard guarantee that nothing can ever - // overlap; the measurement just decides how much to collapse before that clip. + // Responsive toolbar: measure real overflow and progressively collapse items + // right→left until everything fits. Stages: + // 1 code→icon · 2 perm→icon · 3 search label hidden · 4 workDir→icon + // 5 code→menu · 6 perm→menu · 7 search→menu · 8 workDir→menu + // Once items move into the "⋯" overflow menu (≥5) no icon is ever hidden. + // overflow-hidden on the left group is the hard guarantee against any overlap. const toolbarRef = useRef(null) const leftGroupRef = useRef(null) const lastWidthRef = useRef(0) @@ -307,18 +313,18 @@ function ChatInputInner({ return () => ro.disconnect() }, []) - // …or when the set/labels of items changes (workdir name, search/code toggles). + // …or when the set/labels of items changes (these all affect item widths). useLayoutEffect(() => { setCollapseLevel(0) - }, [workDir, searchEnabled, searchAvailable, codeModeEnabled, codeModeFeatureEnabled, lockedModel, activeModelKey]) + }, [workDir, searchEnabled, searchAvailable, codeModeEnabled, codeModeFeatureEnabled, codingAgent, permissionMode, lockedModel, activeModelKey]) // After each render, if the left group still overflows, collapse one more step. // Runs before paint, so the intermediate (overflowing) state is never visible. useLayoutEffect(() => { const el = leftGroupRef.current if (!el) return - if (el.scrollWidth > el.clientWidth + 1 && collapseLevel < 4) { - setCollapseLevel((l) => Math.min(4, l + 1)) + if (el.scrollWidth > el.clientWidth + 1 && collapseLevel < 8) { + setCollapseLevel((l) => Math.min(8, l + 1)) } }) @@ -797,7 +803,7 @@ function ChatInputInner({ />
-
+
@@ -902,7 +908,7 @@ function ChatInputInner({
- {workDir && ( + {workDir && collapseLevel < 8 && ( {/* Level 4: collapse to a square icon */} @@ -935,7 +941,7 @@ function ChatInputInner({ )} - {searchAvailable && ( + {searchAvailable && collapseLevel < 7 && ( )} + {collapseLevel < 6 && (
+ {collapseLevel >= 5 && ( + + + + + + + + More options + + + Options + + {workDir && collapseLevel >= 8 && ( + { void handleSetWorkDir() }}> + + {basename(workDir) || workDir} + + )} + {searchAvailable && collapseLevel >= 7 && ( + e.preventDefault()} + onCheckedChange={(c) => setSearchEnabled(Boolean(c))} + > + Web search + + )} + {collapseLevel >= 6 && ( + e.preventDefault()} + onCheckedChange={(c) => setPermissionMode(c ? 'auto' : 'manual')} + > + Auto-approve actions + + )} + {codeModeFeatureEnabled && collapseLevel >= 5 && ( + <> + e.preventDefault()} + onCheckedChange={(c) => setCodeModeEnabled(Boolean(c))} + > + Code mode + + {codeModeEnabled && ( + { e.preventDefault(); handleToggleCodingAgent() }}> + + Coding agent + {codingAgent === 'claude' ? 'Claude' : 'Codex'} + + )} + + )} + + + )} +
{lockedModel ? (