Skip to content

Commit aef71b6

Browse files
committed
feat: add multi-select when adding repos to filters
Allows selecting multiple repositories from search results before adding them all at once. Changes: - Added checkboxes to repo search results - Repos can be toggled on/off before adding - "Add N Repos" button appears when repos are selected - Already-added repos are shown as disabled with checkmark - Selection is cleared when dropdown closes
1 parent 2020b7e commit aef71b6

File tree

1 file changed

+109
-26
lines changed

1 file changed

+109
-26
lines changed

src/browser/components/home.tsx

Lines changed: 109 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,11 @@ export function Home() {
409409
return () => clearTimeout(timeout);
410410
}, [github, searchQuery]);
411411

412+
// Multi-select state for adding repos
413+
const [selectedReposToAdd, setSelectedReposToAdd] = useState<Set<string>>(
414+
new Set()
415+
);
416+
412417
const handleAddRepo = useCallback((fullName: string) => {
413418
setConfig((prev) => {
414419
if (prev.repos.some((r) => r.name === fullName)) return prev;
@@ -421,6 +426,36 @@ export function Home() {
421426
setSearchResults([]);
422427
}, []);
423428

429+
const handleAddSelectedRepos = useCallback(() => {
430+
if (selectedReposToAdd.size === 0) return;
431+
setConfig((prev) => {
432+
const newRepos = Array.from(selectedReposToAdd)
433+
.filter((name) => !prev.repos.some((r) => r.name === name))
434+
.map((name) => ({ name, mode: "review-requested" as FilterMode }));
435+
if (newRepos.length === 0) return prev;
436+
return {
437+
...prev,
438+
repos: [...prev.repos, ...newRepos],
439+
};
440+
});
441+
setSelectedReposToAdd(new Set());
442+
setSearchQuery("");
443+
setSearchResults([]);
444+
setShowAddRepo(false);
445+
}, [selectedReposToAdd]);
446+
447+
const toggleRepoSelection = useCallback((fullName: string) => {
448+
setSelectedReposToAdd((prev) => {
449+
const next = new Set(prev);
450+
if (next.has(fullName)) {
451+
next.delete(fullName);
452+
} else {
453+
next.add(fullName);
454+
}
455+
return next;
456+
});
457+
}, []);
458+
424459
const handleRemoveRepo = useCallback((repoName: string) => {
425460
setConfig((prev) => ({
426461
...prev,
@@ -814,6 +849,7 @@ export function Home() {
814849
onClick={() => {
815850
setShowAddRepo(false);
816851
setSearchQuery("");
852+
setSelectedReposToAdd(new Set());
817853
}}
818854
/>
819855
<div
@@ -864,32 +900,62 @@ export function Home() {
864900
</button>
865901
)}
866902
{searchResults.length > 0 ? (
867-
searchResults.map((repo) => (
868-
<button
869-
key={repo.id}
870-
onMouseDown={() => {
871-
handleAddRepo(repo.full_name);
872-
setShowAddRepo(false);
873-
setSearchQuery("");
874-
}}
875-
className="w-full flex items-center gap-2 px-3 py-2 hover:bg-muted/50 transition-colors text-left border-b border-border/50 last:border-b-0"
876-
>
877-
{repo.owner && (
878-
<img
879-
src={repo.owner.avatar_url}
880-
alt={repo.owner.login}
881-
className="w-4 h-4 rounded shrink-0"
882-
/>
883-
)}
884-
<span className="font-medium text-xs truncate flex-1">
885-
{repo.full_name}
886-
</span>
887-
<span className="flex items-center gap-1 text-[10px] text-muted-foreground">
888-
<Star className="w-3 h-3" />
889-
{(repo.stargazers_count ?? 0).toLocaleString()}
890-
</span>
891-
</button>
892-
))
903+
searchResults.map((repo) => {
904+
const isSelected = selectedReposToAdd.has(
905+
repo.full_name
906+
);
907+
const isAlreadyAdded = config.repos.some(
908+
(r) => r.name === repo.full_name
909+
);
910+
return (
911+
<button
912+
key={repo.id}
913+
onMouseDown={(e) => {
914+
e.preventDefault();
915+
if (!isAlreadyAdded) {
916+
toggleRepoSelection(repo.full_name);
917+
}
918+
}}
919+
disabled={isAlreadyAdded}
920+
className={cn(
921+
"w-full flex items-center gap-2 px-3 py-2 transition-colors text-left border-b border-border/50 last:border-b-0",
922+
isAlreadyAdded
923+
? "opacity-50 cursor-not-allowed"
924+
: "hover:bg-muted/50 cursor-pointer",
925+
isSelected && "bg-primary/10"
926+
)}
927+
>
928+
<div
929+
className={cn(
930+
"w-4 h-4 rounded border flex items-center justify-center shrink-0 transition-colors",
931+
isSelected
932+
? "bg-primary border-primary"
933+
: isAlreadyAdded
934+
? "border-muted-foreground/30"
935+
: "border-muted-foreground/50"
936+
)}
937+
>
938+
{(isSelected || isAlreadyAdded) && (
939+
<Check className="w-3 h-3 text-primary-foreground" />
940+
)}
941+
</div>
942+
{repo.owner && (
943+
<img
944+
src={repo.owner.avatar_url}
945+
alt={repo.owner.login}
946+
className="w-4 h-4 rounded shrink-0"
947+
/>
948+
)}
949+
<span className="font-medium text-xs truncate flex-1">
950+
{repo.full_name}
951+
</span>
952+
<span className="flex items-center gap-1 text-[10px] text-muted-foreground">
953+
<Star className="w-3 h-3" />
954+
{(repo.stargazers_count ?? 0).toLocaleString()}
955+
</span>
956+
</button>
957+
);
958+
})
893959
) : searchQuery ? (
894960
<div className="px-3 py-4 text-xs text-muted-foreground text-center">
895961
{searching ? "Searching..." : "No repositories found"}
@@ -900,6 +966,23 @@ export function Home() {
900966
</div>
901967
)}
902968
</div>
969+
970+
{/* Add Selected Button */}
971+
{selectedReposToAdd.size > 0 && (
972+
<div className="p-2 border-t border-border">
973+
<button
974+
onMouseDown={(e) => {
975+
e.preventDefault();
976+
handleAddSelectedRepos();
977+
}}
978+
className="w-full flex items-center justify-center gap-2 px-3 py-2 rounded-md bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/90 transition-colors"
979+
>
980+
<Plus className="w-3 h-3" />
981+
Add {selectedReposToAdd.size} Repo
982+
{selectedReposToAdd.size > 1 ? "s" : ""}
983+
</button>
984+
</div>
985+
)}
903986
</div>
904987
</>
905988
)}

0 commit comments

Comments
 (0)