From d0ef5fe4ec36c1dc04485e69f962f6ba5539ddde Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 18 Apr 2026 22:53:23 -0700 Subject: [PATCH] feat(sidebar): consolidate group-by + filter controls (#818) * feat(sidebar): move group-by control into view options menu Consolidates the standalone Group by segmented control into the view options dropdown alongside Sort by and Show properties, and opens the menu to the right of the trigger so it no longer covers the worktree list. * feat(sidebar): combine active + repo filter into single filter control Replaces the separate Active toggle and repo dropdown trigger in the search bar with a single ListFilter icon button that opens a unified dropdown covering Status (Active only) and Repositories. The button expands with an inline summary and accent background when any filter is applied, making the active state obvious, and exposes a Clear filters action. --- .../src/components/sidebar/GroupControls.tsx | 46 ----- .../src/components/sidebar/SearchBar.tsx | 194 ++++++++++-------- .../src/components/sidebar/SidebarHeader.tsx | 38 +++- src/renderer/src/components/sidebar/index.tsx | 2 - 4 files changed, 147 insertions(+), 133 deletions(-) delete mode 100644 src/renderer/src/components/sidebar/GroupControls.tsx diff --git a/src/renderer/src/components/sidebar/GroupControls.tsx b/src/renderer/src/components/sidebar/GroupControls.tsx deleted file mode 100644 index 90e58a1e844..00000000000 --- a/src/renderer/src/components/sidebar/GroupControls.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react' -import { useAppStore } from '@/store' -import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group' - -const GroupControls = React.memo(function GroupControls() { - const groupBy = useAppStore((s) => s.groupBy) - const setGroupBy = useAppStore((s) => s.setGroupBy) - - return ( -
- { - if (v) { - setGroupBy(v as typeof groupBy) - } - }} - variant="outline" - size="sm" - className="h-6 flex-1 justify-start" - > - - All - - - PR Status - - - Repo - - -
- ) -}) - -export default GroupControls diff --git a/src/renderer/src/components/sidebar/SearchBar.tsx b/src/renderer/src/components/sidebar/SearchBar.tsx index 23d30e2b1b2..4ba4757da60 100644 --- a/src/renderer/src/components/sidebar/SearchBar.tsx +++ b/src/renderer/src/components/sidebar/SearchBar.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react' -import { Search, X, Activity, FolderTree, FolderPlus } from 'lucide-react' +import { Search, X, Activity, ListFilter, FolderPlus } from 'lucide-react' import { useAppStore } from '@/store' import { Input } from '@/components/ui/input' import { Button } from '@/components/ui/button' @@ -8,6 +8,7 @@ import { DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, + DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' @@ -37,30 +38,39 @@ const SearchBar = React.memo(function SearchBar() { [filterRepoIds, setFilterRepoIds] ) - const repoTriggerLabel = - selectedRepos.length === 0 ? ( - - - All - - ) : selectedRepos.length === 1 ? ( - - ) : ( - - - {selectedRepos.length} repos - - ) - const handleClear = useCallback(() => setSearchQuery(''), [setSearchQuery]) const handleToggleActive = useCallback( () => setShowActiveOnly(!showActiveOnly), [showActiveOnly, setShowActiveOnly] ) + const canFilterRepos = repos.length > 1 + const hasRepoFilter = canFilterRepos && selectedRepos.length > 0 + const hasAnyFilter = showActiveOnly || hasRepoFilter + const activeFilterCount = (showActiveOnly ? 1 : 0) + (hasRepoFilter ? selectedRepos.length : 0) + + const filterSummary = (() => { + if (!hasAnyFilter) { + return null + } + if (showActiveOnly && !hasRepoFilter) { + return ( + + + Active + + ) + } + if (!showActiveOnly && hasRepoFilter && selectedRepos.length === 1) { + return ( + + ) + } + return {activeFilterCount} filters + })() return (
@@ -78,71 +88,87 @@ const SearchBar = React.memo(function SearchBar() { )} - - - - - - {showActiveOnly ? 'Show all' : 'Active only'} - - - {repos.length > 1 && ( - - - - - - { - setFilterRepoIds([]) - }} - > - All repos - - {repos.map((r) => ( - handleToggleRepo(r.id)} - onSelect={(event) => event.preventDefault()} + + + + + + + + + {hasAnyFilter ? 'Edit filters' : 'Filter worktrees'} + + + + Status + event.preventDefault()} + > + + Active only + + {canFilterRepos && ( + <> + + Repositories + {repos.map((r) => ( + handleToggleRepo(r.id)} + onSelect={(event) => event.preventDefault()} + > + + + ))} + + )} + {hasAnyFilter && ( + <> + + { + setShowActiveOnly(false) + setFilterRepoIds([]) + }} + > + + Clear filters + + + )} + {canFilterRepos && ( + <> + + { + addRepo() + }} + > + + Add repo + + + )} + +
diff --git a/src/renderer/src/components/sidebar/SidebarHeader.tsx b/src/renderer/src/components/sidebar/SidebarHeader.tsx index fb71576f3a5..e8f00eec774 100644 --- a/src/renderer/src/components/sidebar/SidebarHeader.tsx +++ b/src/renderer/src/components/sidebar/SidebarHeader.tsx @@ -3,6 +3,7 @@ import { Plus, SlidersHorizontal } from 'lucide-react' import { useAppStore } from '@/store' import { Button } from '@/components/ui/button' import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip' +import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group' import { isGitRepoKind } from '../../../../shared/repo-kind' import { DropdownMenu, @@ -16,6 +17,12 @@ import { } from '@/components/ui/dropdown-menu' import type { WorktreeCardProperty } from '../../../../shared/types' +const GROUP_BY_OPTIONS = [ + { id: 'none', label: 'All' }, + { id: 'pr-status', label: 'PR Status' }, + { id: 'repo', label: 'Repo' } +] as const + const PROPERTY_OPTIONS: { id: WorktreeCardProperty; label: string }[] = [ { id: 'status', label: 'Terminal status' }, { id: 'unread', label: 'Unread indicator' }, @@ -44,6 +51,8 @@ const SidebarHeader = React.memo(function SidebarHeader() { const toggleWorktreeCardProperty = useAppStore((s) => s.toggleWorktreeCardProperty) const sortBy = useAppStore((s) => s.sortBy) const setSortBy = useAppStore((s) => s.setSortBy) + const groupBy = useAppStore((s) => s.groupBy) + const setGroupBy = useAppStore((s) => s.setGroupBy) return (
@@ -69,7 +78,34 @@ const SidebarHeader = React.memo(function SidebarHeader() { View options - + + Group by +
+ { + if (v) { + setGroupBy(v as typeof groupBy) + } + }} + variant="outline" + size="sm" + className="h-6 w-full justify-start" + > + {GROUP_BY_OPTIONS.map((opt) => ( + + {opt.label} + + ))} + +
+ + Sort by - {/* Virtualized scrollable list */}