app/components/RepositoryBranchSelector.tsx (186 lines of code) (raw):
import React, { useEffect } from "react";
import { RepositoryPicker } from "./RepositoryPicker";
import { RecentDataService } from "~/lib/recentDataService";
interface RepositoryBranchSelectorProps {
// Incoming props are now “controlled” by whatever parent uses them.
selectedRepo: string;
selectedBranch: string;
showRepoDropdown: boolean;
showBranchDropdown: boolean;
recentBranches: any[];
branchSearchInput: string;
onRepoDropdownToggle: () => void;
onBranchDropdownToggle: () => void;
onRepoSelect: (repoUrl: string, defaultBranch?: string) => void;
onBranchSelect: (branch: string) => void;
onBranchSearchChange: (value: string) => void;
onClose: () => void;
}
export const RepositoryBranchSelector = ({
selectedRepo,
selectedBranch,
showRepoDropdown,
showBranchDropdown,
recentBranches,
branchSearchInput,
onRepoDropdownToggle,
onBranchDropdownToggle,
onRepoSelect,
onBranchSelect,
onBranchSearchChange,
onClose,
}: RepositoryBranchSelectorProps) => {
//
// 1) On mount, check localStorage for previously saved values.
// If found, call the “real” callbacks so that the parent knows about them.
//
useEffect(() => {
const savedRepo = localStorage.getItem("selectedRepo");
const savedBranch = localStorage.getItem("selectedBranch");
// Only fire if parent hasn’t already initialized to the same value.
if (savedRepo && savedRepo !== selectedRepo) {
onRepoSelect(savedRepo);
}
if (savedBranch && savedBranch !== selectedBranch) {
onBranchSelect(savedBranch);
}
// We only want this to run once, on mount.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//
// 2) Wrap the incoming callbacks so that they also write to localStorage
//
const handleRepoSelect = (repoUrl: string, defaultBranch?: string) => {
// Persist into localStorage
localStorage.setItem("selectedRepo", repoUrl);
// If the parent also wants to switch branch to defaultBranch, persist that too:
if (defaultBranch) {
localStorage.setItem("selectedBranch", defaultBranch);
}
// Then delegate to the “real” parent callback:
onRepoSelect(repoUrl, defaultBranch);
};
const handleBranchSelect = (branch: string) => {
localStorage.setItem("selectedBranch", branch);
onBranchSelect(branch);
};
return (
<div className="flex items-center gap-3">
{/* Repository Dropdown */}
<div className="relative">
<button
onClick={onRepoDropdownToggle}
className="flex items-center gap-2 rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-700 transition-colors hover:bg-gray-100 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700"
>
<i className="fas fa-folder text-xs text-gray-500"></i>
<span className="max-w-40 truncate" title={selectedRepo}>
{selectedRepo
? selectedRepo.includes("github.com")
? selectedRepo.split("/").slice(-2).join("/")
: selectedRepo
: "Select repository"}
</span>
{/* Show GitHub link icon if it’s a GitHub repo */}
{selectedRepo?.includes("github.com") && (
<i
className="fas fa-link text-xs text-green-500"
title="GitHub repository - private repos accessible if GitHub is connected"
></i>
)}
<i className="fas fa-chevron-down text-xs"></i>
</button>
{showRepoDropdown && (
// Pass our wrapped callback into <RepositoryPicker>
<RepositoryPicker
selectedRepo={selectedRepo}
onRepoSelect={handleRepoSelect}
onClose={onClose}
/>
)}
</div>
{/* Branch Dropdown */}
<div className="relative">
<button
onClick={onBranchDropdownToggle}
className="flex items-center gap-2 rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-700 transition-colors hover:bg-gray-100 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700"
>
<i className="fas fa-code-branch text-xs text-gray-500"></i>
<span>{selectedBranch || "Select branch"}</span>
<i className="fas fa-chevron-down text-xs"></i>
</button>
{showBranchDropdown && (
<div className="absolute bottom-full left-0 z-10 mb-2 w-64 rounded-lg border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-800">
<div className="p-3">
<input
type="text"
placeholder="Type or search branches..."
value={branchSearchInput}
onChange={(e) => onBranchSearchChange(e.target.value)}
onKeyPress={(e) => {
if (e.key === "Enter" && branchSearchInput.trim()) {
handleBranchSelect(branchSearchInput.trim());
}
}}
className="w-full rounded-md border border-gray-300 bg-gray-50 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
autoFocus
/>
{branchSearchInput.trim() && (
<div className="mb-2 mt-2">
<div
className="flex cursor-pointer items-center gap-2 rounded border border-blue-200 bg-blue-50 px-3 py-2 text-sm hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900/30 dark:hover:bg-blue-900/50"
onClick={() => handleBranchSelect(branchSearchInput.trim())}
>
<i className="fas fa-plus text-xs text-blue-600 dark:text-blue-400"></i>
<span className="text-blue-700 dark:text-blue-300">
Use "{branchSearchInput.trim()}"
</span>
</div>
</div>
)}
{/* Recent Branches */}
{recentBranches.length > 0 && (
<div className="mb-2 border-t border-gray-200 pt-2 dark:border-gray-600">
<div className="mb-2 px-1 text-xs text-gray-500 dark:text-gray-400">
Recent branches:
</div>
<div className="max-h-24 overflow-y-auto">
{recentBranches
.filter(
(branch) =>
!branchSearchInput ||
branch.name
.toLowerCase()
.includes(branchSearchInput.toLowerCase())
)
.map((branch) => (
<div
key={branch.name}
className="flex cursor-pointer items-center gap-2 rounded px-3 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700"
onClick={() => handleBranchSelect(branch.name)}
>
<i className="fas fa-history text-xs text-blue-500"></i>
<span>{branch.name}</span>
</div>
))}
</div>
</div>
)}
{/* Common Branches */}
<div className="border-t border-gray-200 pt-2 dark:border-gray-600">
<div className="mb-2 px-1 text-xs text-gray-500 dark:text-gray-400">
Common branches:
</div>
<div className="max-h-32 overflow-y-auto">
{RecentDataService.getCommonBranches()
.filter(
(branch) =>
!branchSearchInput ||
branch
.toLowerCase()
.includes(branchSearchInput.toLowerCase())
)
.map((branch) => (
<div
key={branch}
className="flex cursor-pointer items-center gap-2 rounded px-3 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700"
onClick={() => handleBranchSelect(branch)}
>
<i className="fas fa-code-branch text-xs"></i>
<span>{branch}</span>
</div>
))}
</div>
</div>
</div>
</div>
)}
</div>
</div>
);
};