mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 14:15:24 +00:00
111 lines
2.9 KiB
TypeScript
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>
|
|
);
|
|
}
|