Files
Termix/plugins/ssh-terminal/src/frontend/terminal/command-history/CommandAutocomplete.tsx
T

111 lines
2.9 KiB
TypeScript

import React, { useEffect, useRef } from "react";
import { cn } from "@termix/plugin-sdk/ui";
interface CommandAutocompleteProps {
suggestions: string[];
selectedIndex: number;
onSelect: (command: string) => void;
position: { top: number; left: number };
visible: boolean;
}
interface CommandAutosuggestionProps {
suggestion: string;
position: { top: number; left: number };
style?: React.CSSProperties;
visible: boolean;
}
export function CommandAutosuggestion({
suggestion,
position,
style,
visible,
}: CommandAutosuggestionProps) {
if (!visible || !suggestion) {
return null;
}
return (
<div
className="pointer-events-none fixed z-[9998] whitespace-pre text-muted-foreground/45"
style={{
top: `${position.top}px`,
left: `${position.left}px`,
fontVariantLigatures: "none",
fontFeatureSettings: '"liga" 0, "calt" 0',
textRendering: "optimizeLegibility",
WebkitFontSmoothing: "antialiased",
transition: "opacity 80ms ease-out",
willChange: "contents",
...style,
}}
>
{suggestion}
</div>
);
}
export function CommandAutocomplete({
suggestions,
selectedIndex,
onSelect,
position,
visible,
}: CommandAutocompleteProps) {
const containerRef = useRef<HTMLDivElement>(null);
const selectedRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (selectedRef.current && containerRef.current) {
selectedRef.current.scrollIntoView({
block: "nearest",
behavior: "smooth",
});
}
}, [selectedIndex]);
if (!visible || suggestions.length === 0) {
return null;
}
const footerHeight = 32;
const maxSuggestionsHeight = 240 - footerHeight;
return (
<div
ref={containerRef}
className="fixed z-[9999] bg-popover border border-border shadow-lg min-w-[200px] max-w-[600px] flex flex-col"
style={{
top: `${position.top}px`,
left: `${position.left}px`,
maxHeight: "240px",
}}
>
<div
className="overflow-y-auto thin-scrollbar"
style={{ maxHeight: `${maxSuggestionsHeight}px` }}
>
{suggestions.map((suggestion, index) => (
<div
key={index}
ref={index === selectedIndex ? selectedRef : null}
className={cn(
"px-3 py-1.5 text-sm font-mono cursor-pointer transition-colors text-foreground",
"hover:bg-muted",
index === selectedIndex && "bg-accent text-accent-foreground",
)}
onClick={() => onSelect(suggestion)}
onMouseEnter={() => {}}
>
{suggestion}
</div>
))}
</div>
<div className="px-3 py-1 text-xs text-muted-foreground border-t border-border bg-muted/50 shrink-0">
Tab/Enter to complete • ↑↓ to navigate • Esc to close
</div>
</div>
);
}