feat: add tabs to SlashCommandPicker for better command organization

- Add Default and Custom tabs to separate built-in vs user/project commands
- Enhance command grouping with User/Project scope indicators and icons
- Improve filtering logic to work with tab selection
- Add visual distinction between user and project commands with icons
- Maintain backward compatibility with existing command display logic
This commit is contained in:
Vivek R
2025-07-07 23:10:16 +05:30
parent 8af922944b
commit a3d7011c43

View File

@@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { api } from "@/lib/api"; import { api } from "@/lib/api";
import { import {
X, X,
@@ -11,7 +12,9 @@ import {
Zap, Zap,
FileCode, FileCode,
Terminal, Terminal,
AlertCircle AlertCircle,
User,
Building2
} from "lucide-react"; } from "lucide-react";
import type { SlashCommand } from "@/lib/api"; import type { SlashCommand } from "@/lib/api";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -81,6 +84,7 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [selectedIndex, setSelectedIndex] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0);
const [searchQuery, setSearchQuery] = useState(initialQuery); const [searchQuery, setSearchQuery] = useState(initialQuery);
const [activeTab, setActiveTab] = useState<string>("custom");
const commandListRef = useRef<HTMLDivElement>(null); const commandListRef = useRef<HTMLDivElement>(null);
@@ -89,7 +93,7 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
loadCommands(); loadCommands();
}, [projectPath]); }, [projectPath]);
// Filter commands based on search query // Filter commands based on search query and active tab
useEffect(() => { useEffect(() => {
if (!commands.length) { if (!commands.length) {
setFilteredCommands([]); setFilteredCommands([]);
@@ -97,11 +101,23 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
} }
const query = searchQuery.toLowerCase(); const query = searchQuery.toLowerCase();
let filteredByTab: SlashCommand[];
if (!query) { // Filter by active tab
setFilteredCommands(commands); if (activeTab === "default") {
// No default/built-in commands yet
filteredByTab = [];
} else { } else {
const filtered = commands.filter(cmd => { // Show all custom commands (both user and project)
filteredByTab = commands;
}
// Then filter by search query
let filtered: SlashCommand[];
if (!query) {
filtered = filteredByTab;
} else {
filtered = filteredByTab.filter(cmd => {
// Match against command name // Match against command name
if (cmd.name.toLowerCase().includes(query)) return true; if (cmd.name.toLowerCase().includes(query)) return true;
@@ -134,13 +150,13 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
// Then alphabetically // Then alphabetically
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}); });
}
setFilteredCommands(filtered); setFilteredCommands(filtered);
}
// Reset selected index when filtered list changes // Reset selected index when filtered list changes
setSelectedIndex(0); setSelectedIndex(0);
}, [searchQuery, commands]); }, [searchQuery, commands, activeTab]);
// Keyboard navigation // Keyboard navigation
useEffect(() => { useEffect(() => {
@@ -205,9 +221,17 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
onSelect(command); onSelect(command);
}; };
// Group commands by namespace (or "Commands" if no namespace) // Group commands by scope and namespace for the Custom tab
const groupedCommands = filteredCommands.reduce((acc, cmd) => { const groupedCommands = filteredCommands.reduce((acc, cmd) => {
const key = cmd.namespace || "Commands"; let key: string;
if (cmd.scope === "user") {
key = cmd.namespace ? `User Commands: ${cmd.namespace}` : "User Commands";
} else if (cmd.scope === "project") {
key = cmd.namespace ? `Project Commands: ${cmd.namespace}` : "Project Commands";
} else {
key = cmd.namespace || "Commands";
}
if (!acc[key]) { if (!acc[key]) {
acc[key] = []; acc[key] = [];
} }
@@ -254,6 +278,16 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
<X className="h-4 w-4" /> <X className="h-4 w-4" />
</Button> </Button>
</div> </div>
{/* Tabs */}
<div className="mt-3">
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="default">Default</TabsTrigger>
<TabsTrigger value="custom">Custom</TabsTrigger>
</TabsList>
</Tabs>
</div>
</div> </div>
{/* Command List */} {/* Command List */}
@@ -271,11 +305,29 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
</div> </div>
)} )}
{!isLoading && !error && filteredCommands.length === 0 && ( {!isLoading && !error && (
<>
{/* Default Tab Content */}
{activeTab === "default" && (
<div className="flex flex-col items-center justify-center h-full">
<Command className="h-8 w-8 text-muted-foreground mb-2" />
<span className="text-sm text-muted-foreground">
No default commands available
</span>
<p className="text-xs text-muted-foreground mt-2 text-center px-4">
Default commands are built-in system commands
</p>
</div>
)}
{/* Custom Tab Content */}
{activeTab === "custom" && (
<>
{filteredCommands.length === 0 && (
<div className="flex flex-col items-center justify-center h-full"> <div className="flex flex-col items-center justify-center h-full">
<Search className="h-8 w-8 text-muted-foreground mb-2" /> <Search className="h-8 w-8 text-muted-foreground mb-2" />
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
{searchQuery ? 'No commands found' : 'No commands available'} {searchQuery ? 'No commands found' : 'No custom commands available'}
</span> </span>
{!searchQuery && ( {!searchQuery && (
<p className="text-xs text-muted-foreground mt-2 text-center px-4"> <p className="text-xs text-muted-foreground mt-2 text-center px-4">
@@ -285,7 +337,7 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
</div> </div>
)} )}
{!isLoading && !error && filteredCommands.length > 0 && ( {filteredCommands.length > 0 && (
<div className="p-2" ref={commandListRef}> <div className="p-2" ref={commandListRef}>
{/* If no grouping needed, show flat list */} {/* If no grouping needed, show flat list */}
{Object.keys(groupedCommands).length === 1 ? ( {Object.keys(groupedCommands).length === 1 ? (
@@ -356,7 +408,9 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
<div className="space-y-4"> <div className="space-y-4">
{Object.entries(groupedCommands).map(([groupKey, groupCommands]) => ( {Object.entries(groupedCommands).map(([groupKey, groupCommands]) => (
<div key={groupKey}> <div key={groupKey}>
<h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wider px-3 mb-1"> <h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wider px-3 mb-1 flex items-center gap-2">
{groupKey.startsWith("User Commands") && <User className="h-3 w-3" />}
{groupKey.startsWith("Project Commands") && <Building2 className="h-3 w-3" />}
{groupKey} {groupKey}
</h3> </h3>
@@ -429,6 +483,10 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
)} )}
</div> </div>
)} )}
</>
)}
</>
)}
</div> </div>
{/* Footer */} {/* Footer */}