Skip to main content
Glama
finder.tsx5.15 kB
"use client"; import * as React from "react"; import { CommandMenu, CommandMenuTrigger, CommandMenuContent, CommandMenuInput, CommandMenuList, CommandMenuGroup, CommandMenuItem, CommandMenuSeparator, useCommandMenuShortcut, CommandMenuEmpty, useFinderMenuShortcut, } from "@/components/ui/command-menu"; import { Button } from "@/components/ui/button"; import { Kbd } from "@/components/ui/kbd"; import { Command, Calendar, User, Settings, Plus, Upload, Download, Search, FileText, Home, List, Folder, File, } from "lucide-react"; import { useRouter } from "next/navigation"; import { RunIcon } from "@codesandbox/sandpack-react"; import { Tree, DirEntry } from "../sandbox/FileTree"; // Utility function to detect OS and return appropriate modifier key const getModifierKey = () => { return { key: "cmd", symbol: "⌘" }; }; type FileItem = { name: string; path: string; isDir: boolean; icon: React.JSX.Element; type: "file" | "directory"; }; export const FileFinder = ({ tree, handleFile, handleDir, }: { tree: Tree; handleFile: (path: string) => void; handleDir: (path: string) => void; }) => { const router = useRouter(); const [open, setOpen] = React.useState(false); const [value, setValue] = React.useState(""); useFinderMenuShortcut(() => setOpen(true)); // Convert tree structure to flat array of items const allItems = React.useMemo(() => { const items: FileItem[] = []; Object.entries(tree).forEach(([path, entries]) => { entries.forEach((entry) => { const fullPath = path === "/" ? `/${entry.name}` : `${path}/${entry.name}`; items.push({ name: entry.name, path: fullPath, isDir: entry.isDir, icon: entry.isDir ? <Folder size={16} /> : <File size={16} />, type: entry.isDir ? "directory" : "file", }); }); }); return items; }, [tree]); const filteredItems = React.useMemo(() => { if (!value) return allItems; return allItems.filter( (item) => item.name.toLowerCase().includes(value.toLowerCase()) || item.path.toLowerCase().includes(value.toLowerCase()), ); }, [value, allItems]); const groupedItems = React.useMemo(() => { const groups: Record<string, FileItem[]> = { directory: [], file: [], }; filteredItems.forEach((item) => { groups[item.type].push(item); }); // Remove empty groups Object.keys(groups).forEach((key) => { if (groups[key].length === 0) { delete groups[key]; } }); return groups; }, [filteredItems]); const getGroupTitle = (type: string) => { switch (type) { case "directory": return "Directories"; case "file": return "Files"; default: return type; } }; const handleItemSelect = (item: FileItem) => { if (item.isDir) { // handleDir(item.path); } else { handleFile(item.path); } setOpen(false); setValue(""); }; let globalIndex = 0; return ( <CommandMenu open={open} onOpenChange={setOpen}> <CommandMenuTrigger asChild> <kbd className="transition-colors duration-150 hover:bg-emerald-800 select-none items-center gap-1 rounded border border-border bg-muted px-2 text-lg font-jetbrains-mono font-medium opacity-100 ml-auto flex"> ⌘ P </kbd> </CommandMenuTrigger> <CommandMenuContent className="rounded-xl outline-2 outline-[var(--app-accent)] outline-offset-2"> <CommandMenuInput placeholder="Type to search files and directories..." value={value} onChange={(e) => setValue(e.target.value)} /> <CommandMenuList maxHeight="400px"> {Object.keys(groupedItems).length === 0 ? ( <CommandMenuEmpty>No results found for "{value}"</CommandMenuEmpty> ) : ( Object.entries(groupedItems).map(([type, items], groupIndex) => ( <React.Fragment key={type}> {groupIndex > 0 && <CommandMenuSeparator />} <CommandMenuGroup heading={getGroupTitle(type)}> {items.map((item, index) => { const currentIndex = globalIndex++; return ( <CommandMenuItem key={`${type}-${index}-${item.path}`} icon={item.icon} index={currentIndex} onSelect={() => handleItemSelect(item)} > <div className="flex flex-col items-start"> <span>{item.name}</span> <span className="text-xs text-muted-foreground"> {item.path} </span> </div> </CommandMenuItem> ); })} </CommandMenuGroup> </React.Fragment> )) )} </CommandMenuList> </CommandMenuContent> </CommandMenu> ); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ParthKapoor-dev/devex'

If you have feedback or need assistance with the MCP directory API, please join our Discord server