Files
NetMesh/components/snippets/SnippetCommandPicker.tsx
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

129 lines
4.8 KiB
TypeScript

import { FileCode, Search, Zap } from 'lucide-react';
import React, { memo, useMemo, useState } from 'react';
import { useI18n } from '../../application/i18n/I18nProvider';
import { cn } from '../../lib/utils';
import type { Snippet } from '../../types';
import { FixedSizeVirtualList } from '../ui/FixedSizeVirtualList';
import { Input } from '../ui/input';
import { SnippetCommandTooltipContent } from './SnippetCommandTooltipContent';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip';
const ROW_HEIGHT = 34;
export interface SnippetCommandPickerProps {
snippets: Snippet[];
selectedId?: string | null;
onSelect: (snippet: Snippet) => void;
className?: string;
showTitle?: boolean;
}
export const SnippetCommandPicker = memo(function SnippetCommandPicker({
snippets,
selectedId,
onSelect,
className,
showTitle = true,
}: SnippetCommandPickerProps) {
const { t } = useI18n();
const [search, setSearch] = useState('');
const filtered = useMemo(() => {
const q = search.trim().toLowerCase();
const sorted = [...snippets].sort((a, b) => a.label.localeCompare(b.label));
if (!q) return sorted;
return sorted.filter(
(snippet) =>
snippet.label.toLowerCase().includes(q)
|| snippet.command.toLowerCase().includes(q)
|| (snippet.package || '').toLowerCase().includes(q),
);
}, [search, snippets]);
const listItems = useMemo(
() => filtered.map((snippet) => ({ key: snippet.id, snippet })),
[filtered],
);
return (
<TooltipProvider delayDuration={300}>
<div
className={cn(
'flex min-h-0 flex-col overflow-hidden rounded-md border border-border/60 bg-muted/20',
className,
)}
>
<div className="shrink-0 border-b border-border/50 px-2 py-1.5">
{showTitle && (
<p className="mb-1.5 px-0.5 text-[10px] font-medium text-muted-foreground">
{t('systemManager.tmux.pickSnippet')}
</p>
)}
<div className="relative">
<Search size={12} className="absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder={t('snippets.searchPlaceholder')}
className="h-7 border-none bg-background/60 pl-7 text-xs"
disabled={snippets.length === 0}
/>
</div>
</div>
<div className="min-h-0 flex-1">
{snippets.length === 0 ? (
<div className="flex h-full flex-col items-center justify-center px-4 py-6 text-center text-muted-foreground">
<Zap size={22} className="mb-2 opacity-40" />
<p className="text-xs">{t('systemManager.tmux.pickSnippetEmpty')}</p>
</div>
) : filtered.length === 0 ? (
<div className="px-3 py-4 text-center text-xs italic text-muted-foreground">
{t('common.noResultsFound')}
</div>
) : (
<FixedSizeVirtualList
className="h-full"
contentClassName="py-1"
items={listItems}
itemHeight={ROW_HEIGHT}
getItemKey={(item) => item.key}
renderItem={(item) => {
const { snippet } = item;
const selected = selectedId === snippet.id;
return (
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={() => onSelect(snippet)}
className={cn(
'flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left transition-colors',
selected
? 'bg-primary/10 text-foreground'
: 'hover:bg-accent/50',
)}
>
<FileCode size={12} className="shrink-0 text-muted-foreground" />
<span className="min-w-0 flex-1 truncate text-xs font-medium">{snippet.label}</span>
{snippet.package ? (
<span className="max-w-[42%] shrink-0 truncate text-[10px] text-muted-foreground">
{snippet.package}
</span>
) : null}
</button>
</TooltipTrigger>
<TooltipContent side="right" align="start">
<SnippetCommandTooltipContent label={snippet.label} command={snippet.command} />
</TooltipContent>
</Tooltip>
);
}}
/>
)}
</div>
</div>
</TooltipProvider>
);
});