claude-code

This commit is contained in:
ashutoshpythoncs@gmail.com
2026-03-31 18:58:05 +05:30
parent a2a44a5841
commit b564857c0b
2148 changed files with 564518 additions and 2 deletions

View File

@@ -0,0 +1,249 @@
"use client";
import { useEffect, useRef, useState, useMemo } from "react";
import * as Dialog from "@radix-ui/react-dialog";
import { Search, Clock } from "lucide-react";
import { useCommandRegistry } from "@/hooks/useCommandRegistry";
import { CommandPaletteItem } from "./CommandPaletteItem";
import { SHORTCUT_CATEGORIES } from "@/lib/shortcuts";
import type { Command, ShortcutCategory } from "@/lib/shortcuts";
import { cn } from "@/lib/utils";
/** Fuzzy match: every character of query must appear in order in target */
function fuzzyMatch(target: string, query: string): boolean {
if (!query) return true;
const t = target.toLowerCase();
const q = query.toLowerCase();
let qi = 0;
for (let i = 0; i < t.length && qi < q.length; i++) {
if (t[i] === q[qi]) qi++;
}
return qi === q.length;
}
/** Score a command against a search query (higher = better) */
function score(cmd: Command, query: string): number {
const q = query.toLowerCase();
const label = cmd.label.toLowerCase();
if (label === q) return 100;
if (label.startsWith(q)) return 80;
if (label.includes(q)) return 60;
if (cmd.description.toLowerCase().includes(q)) return 40;
if (fuzzyMatch(label, q)) return 20;
return 0;
}
interface GroupedResults {
label: string;
commands: Command[];
}
export function CommandPalette() {
const {
paletteOpen,
closePalette,
commands,
runCommand,
recentCommandIds,
openHelp,
} = useCommandRegistry();
const [query, setQuery] = useState("");
const [activeIndex, setActiveIndex] = useState(0);
const inputRef = useRef<HTMLInputElement>(null);
const listRef = useRef<HTMLDivElement>(null);
// Reset state when palette opens
useEffect(() => {
if (paletteOpen) {
setQuery("");
setActiveIndex(0);
// Small delay to let the dialog animate in before focusing
setTimeout(() => inputRef.current?.focus(), 10);
}
}, [paletteOpen]);
const filteredGroups = useMemo<GroupedResults[]>(() => {
if (!query.trim()) {
// Show recents first, then all categories
const recentCmds = recentCommandIds
.map((id) => commands.find((c) => c.id === id))
.filter((c): c is Command => !!c);
const groups: GroupedResults[] = [];
if (recentCmds.length > 0) {
groups.push({ label: "Recent", commands: recentCmds });
}
for (const cat of SHORTCUT_CATEGORIES) {
const catCmds = commands.filter((c) => c.category === cat);
if (catCmds.length > 0) {
groups.push({ label: cat, commands: catCmds });
}
}
return groups;
}
// Search mode: flat scored list, re-grouped by category
const scored = commands
.map((cmd) => ({ cmd, s: score(cmd, query) }))
.filter(({ s }) => s > 0)
.sort((a, b) => b.s - a.s)
.map(({ cmd }) => cmd);
if (scored.length === 0) return [];
const byCategory: Partial<Record<ShortcutCategory, Command[]>> = {};
for (const cmd of scored) {
if (!byCategory[cmd.category]) byCategory[cmd.category] = [];
byCategory[cmd.category]!.push(cmd);
}
return SHORTCUT_CATEGORIES.filter((c) => byCategory[c]?.length).map(
(c) => ({ label: c, commands: byCategory[c]! })
);
}, [query, commands, recentCommandIds]);
const flatResults = useMemo(
() => filteredGroups.flatMap((g) => g.commands),
[filteredGroups]
);
// Clamp activeIndex when results change
useEffect(() => {
setActiveIndex((i) => Math.min(i, Math.max(flatResults.length - 1, 0)));
}, [flatResults.length]);
// Scroll active item into view
useEffect(() => {
const el = listRef.current?.querySelector(`[data-index="${activeIndex}"]`);
el?.scrollIntoView({ block: "nearest" });
}, [activeIndex]);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "ArrowDown") {
e.preventDefault();
setActiveIndex((i) => Math.min(i + 1, flatResults.length - 1));
} else if (e.key === "ArrowUp") {
e.preventDefault();
setActiveIndex((i) => Math.max(i - 1, 0));
} else if (e.key === "Enter") {
e.preventDefault();
const cmd = flatResults[activeIndex];
if (cmd) {
closePalette();
runCommand(cmd.id);
}
}
};
const handleSelect = (cmd: Command) => {
closePalette();
runCommand(cmd.id);
};
let flatIdx = 0;
return (
<Dialog.Root open={paletteOpen} onOpenChange={(open) => !open && closePalette()}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" />
<Dialog.Content
className={cn(
"fixed left-1/2 top-[20%] -translate-x-1/2 z-50",
"w-full max-w-xl",
"bg-surface-900 border border-surface-700 rounded-xl shadow-2xl",
"data-[state=open]:animate-in data-[state=closed]:animate-out",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
"data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-left-1/2",
"data-[state=closed]:slide-out-to-top-[18%] data-[state=open]:slide-in-from-top-[18%]"
)}
onKeyDown={handleKeyDown}
aria-label="Command palette"
>
{/* Search input */}
<div className="flex items-center gap-2 px-3 py-3 border-b border-surface-800">
<Search className="w-4 h-4 text-surface-500 flex-shrink-0" />
<input
ref={inputRef}
value={query}
onChange={(e) => {
setQuery(e.target.value);
setActiveIndex(0);
}}
placeholder="Search commands..."
className={cn(
"flex-1 bg-transparent text-sm text-surface-100",
"placeholder:text-surface-500 focus:outline-none"
)}
/>
<kbd className="hidden sm:inline-flex items-center h-5 px-1.5 rounded text-[10px] font-mono bg-surface-800 border border-surface-700 text-surface-500">
Esc
</kbd>
</div>
{/* Results */}
<div
ref={listRef}
role="listbox"
className="overflow-y-auto max-h-[360px] py-1"
>
{filteredGroups.length === 0 ? (
<div className="py-10 text-center text-sm text-surface-500">
No commands found
</div>
) : (
filteredGroups.map((group) => (
<div key={group.label}>
<div className="flex items-center gap-2 px-3 py-1.5">
{group.label === "Recent" && (
<Clock className="w-3 h-3 text-surface-600" />
)}
<span className="text-[10px] font-semibold uppercase tracking-wider text-surface-600">
{group.label}
</span>
</div>
{group.commands.map((cmd) => {
const idx = flatIdx++;
return (
<div key={cmd.id} data-index={idx}>
<CommandPaletteItem
command={cmd}
isActive={idx === activeIndex}
onSelect={() => handleSelect(cmd)}
onHighlight={() => setActiveIndex(idx)}
/>
</div>
);
})}
</div>
))
)}
</div>
{/* Footer */}
<div className="flex items-center gap-4 px-3 py-2 border-t border-surface-800 text-[10px] text-surface-600">
<span className="flex items-center gap-1">
<kbd className="inline-flex items-center h-4 px-1 rounded bg-surface-800 border border-surface-700 font-mono"></kbd>
navigate
</span>
<span className="flex items-center gap-1">
<kbd className="inline-flex items-center h-4 px-1 rounded bg-surface-800 border border-surface-700 font-mono"></kbd>
select
</span>
<span className="flex items-center gap-1">
<kbd className="inline-flex items-center h-4 px-1 rounded bg-surface-800 border border-surface-700 font-mono">Esc</kbd>
close
</span>
<button
onClick={() => { closePalette(); openHelp(); }}
className="ml-auto hover:text-surface-300 transition-colors"
>
? View all shortcuts
</button>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}

View File

@@ -0,0 +1,89 @@
"use client";
import {
MessageSquarePlus,
Trash2,
Settings,
Sun,
Search,
HelpCircle,
PanelLeftClose,
ChevronRight,
Zap,
type LucideIcon,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { ShortcutBadge } from "@/components/shortcuts/ShortcutBadge";
import type { Command } from "@/lib/shortcuts";
const ICON_MAP: Record<string, LucideIcon> = {
MessageSquarePlus,
Trash2,
Settings,
Sun,
Search,
HelpCircle,
PanelLeftClose,
ChevronRight,
Zap,
};
interface CommandPaletteItemProps {
command: Command;
isActive: boolean;
onSelect: () => void;
onHighlight: () => void;
}
export function CommandPaletteItem({
command,
isActive,
onSelect,
onHighlight,
}: CommandPaletteItemProps) {
const Icon = command.icon ? (ICON_MAP[command.icon] ?? ChevronRight) : ChevronRight;
return (
<div
role="option"
aria-selected={isActive}
onClick={onSelect}
onMouseEnter={onHighlight}
className={cn(
"flex items-center gap-3 px-3 py-2.5 cursor-pointer select-none",
"transition-colors",
isActive ? "bg-brand-600/20 text-surface-100" : "text-surface-300 hover:bg-surface-800"
)}
>
<span
className={cn(
"flex-shrink-0 w-7 h-7 rounded-md flex items-center justify-center",
isActive ? "bg-brand-600/30 text-brand-400" : "bg-surface-800 text-surface-500"
)}
>
<Icon className="w-3.5 h-3.5" />
</span>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">{command.label}</p>
{command.description && (
<p className="text-xs text-surface-500 truncate">{command.description}</p>
)}
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<span
className={cn(
"text-[10px] px-1.5 py-0.5 rounded border font-medium",
isActive
? "border-brand-600/40 text-brand-400 bg-brand-600/10"
: "border-surface-700 text-surface-600 bg-surface-800"
)}
>
{command.category}
</span>
{command.keys.length > 0 && <ShortcutBadge keys={command.keys} />}
</div>
</div>
);
}