Files
huskies/frontend/src/components/selection/ProjectPathInput.tsx
T
dave f610ef6046 Restore codebase deleted by bad auto-commit e4227cf
Commit e4227cf (a story creation auto-commit) erroneously deleted 175
files from master's tree, likely due to a race condition between
concurrent git operations. This commit re-adds all files from the
working directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 19:07:07 +00:00

171 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export interface ProjectPathMatch {
name: string;
path: string;
}
export interface ProjectPathInputProps {
value: string;
onChange: (value: string) => void;
onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void;
suggestionTail: string;
matchList: ProjectPathMatch[];
selectedMatch: number;
onSelectMatch: (index: number) => void;
onAcceptMatch: (path: string) => void;
onCloseSuggestions: () => void;
currentPartial: string;
}
function renderHighlightedMatch(text: string, query: string) {
if (!query) return text;
let qIndex = 0;
const lowerQuery = query.toLowerCase();
const counts = new Map<string, number>();
return text.split("").map((char) => {
const isMatch =
qIndex < lowerQuery.length && char.toLowerCase() === lowerQuery[qIndex];
if (isMatch) {
qIndex += 1;
}
const count = counts.get(char) ?? 0;
counts.set(char, count + 1);
return (
<span
key={`${char}-${count}`}
style={isMatch ? { fontWeight: 600, color: "#222" } : undefined}
>
{char}
</span>
);
});
}
export function ProjectPathInput({
value,
onChange,
onKeyDown,
suggestionTail,
matchList,
selectedMatch,
onSelectMatch,
onAcceptMatch,
onCloseSuggestions,
currentPartial,
}: ProjectPathInputProps) {
return (
<div
style={{
position: "relative",
marginTop: "12px",
marginBottom: "170px",
}}
>
<div
style={{
position: "absolute",
inset: 0,
padding: "10px",
color: "#aaa",
fontFamily: "monospace",
whiteSpace: "pre",
overflow: "hidden",
textOverflow: "ellipsis",
pointerEvents: "none",
}}
>
{value}
{suggestionTail}
</div>
<input
type="text"
value={value}
placeholder="/path/to/project"
onChange={(event) => onChange(event.target.value)}
onKeyDown={onKeyDown}
style={{
width: "100%",
padding: "10px",
fontFamily: "monospace",
background: "transparent",
position: "relative",
zIndex: 1,
}}
/>
{matchList.length > 0 && (
<div
style={{
position: "absolute",
top: "100%",
left: 0,
right: 0,
marginTop: "6px",
border: "1px solid #ddd",
borderRadius: "6px",
overflow: "hidden",
background: "#fff",
fontFamily: "monospace",
height: "160px",
overflowY: "auto",
boxSizing: "border-box",
zIndex: 2,
}}
>
<div
style={{
display: "flex",
justifyContent: "flex-end",
alignItems: "center",
padding: "4px 6px",
borderBottom: "1px solid #eee",
background: "#fafafa",
}}
>
<button
type="button"
aria-label="Close suggestions"
onClick={onCloseSuggestions}
style={{
width: "24px",
height: "24px",
borderRadius: "4px",
border: "1px solid #ddd",
background: "#fff",
cursor: "pointer",
lineHeight: 1,
}}
>
×
</button>
</div>
{matchList.map((match, index) => {
const isSelected = index === selectedMatch;
return (
<button
key={match.path}
type="button"
onMouseEnter={() => onSelectMatch(index)}
onMouseDown={(event) => {
event.preventDefault();
onSelectMatch(index);
onAcceptMatch(match.path);
}}
style={{
width: "100%",
textAlign: "left",
padding: "6px 8px",
border: "none",
background: isSelected ? "#f0f0f0" : "transparent",
cursor: "pointer",
fontFamily: "inherit",
}}
>
{renderHighlightedMatch(match.name, currentPartial)}/
</button>
);
})}
</div>
)}
</div>
);
}