@@ -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