mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
refactor(board-view): extract BoardSearchBar component
This commit is contained in:
@@ -64,6 +64,7 @@ import { BoardBackgroundModal } from "@/components/dialogs/board-background-moda
|
||||
import { AddFeatureDialog } from "./board-view/AddFeatureDialog";
|
||||
import { EditFeatureDialog } from "./board-view/EditFeatureDialog";
|
||||
import { BoardHeader } from "./board-view/BoardHeader";
|
||||
import { BoardSearchBar } from "./board-view/BoardSearchBar";
|
||||
import {
|
||||
Plus,
|
||||
RefreshCw,
|
||||
@@ -285,7 +286,6 @@ export function BoardView() {
|
||||
const startNextFeaturesRef = useRef<() => void>(() => {});
|
||||
|
||||
// Ref for search input to enable keyboard shortcut focus
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// Keyboard shortcuts for this view
|
||||
const boardShortcuts: KeyboardShortcut[] = useMemo(() => {
|
||||
@@ -300,11 +300,6 @@ export function BoardView() {
|
||||
action: () => startNextFeaturesRef.current(),
|
||||
description: "Start next features from backlog",
|
||||
},
|
||||
{
|
||||
key: "/",
|
||||
action: () => searchInputRef.current?.focus(),
|
||||
description: "Focus search input",
|
||||
},
|
||||
];
|
||||
|
||||
// Add shortcuts for in-progress cards (1-9 and 0 for 10th)
|
||||
@@ -1735,51 +1730,13 @@ export function BoardView() {
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Search Bar Row */}
|
||||
<div className="px-4 pt-4 pb-2 flex items-center justify-between">
|
||||
<div className="relative max-w-md flex-1 flex items-center gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground pointer-events-none" />
|
||||
<Input
|
||||
ref={searchInputRef}
|
||||
type="text"
|
||||
placeholder="Search features by keyword..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9 pr-12 border-border"
|
||||
data-testid="kanban-search-input"
|
||||
/>
|
||||
{searchQuery ? (
|
||||
<button
|
||||
onClick={() => setSearchQuery("")}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded-sm hover:bg-accent text-muted-foreground hover:text-foreground transition-colors"
|
||||
data-testid="kanban-search-clear"
|
||||
aria-label="Clear search"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
) : (
|
||||
<span
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-brand-500/10 border border-brand-500/30 text-brand-400/70"
|
||||
data-testid="kanban-search-hotkey"
|
||||
>
|
||||
/
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Spec Creation Loading Badge */}
|
||||
{isCreatingSpec &&
|
||||
currentProject?.path === creatingSpecProjectPath && (
|
||||
<div
|
||||
className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-brand-500/10 border border-brand-500/20 shrink-0"
|
||||
title="Creating App Specification"
|
||||
data-testid="spec-creation-badge"
|
||||
>
|
||||
<Loader2 className="w-3 h-3 animate-spin text-brand-500 shrink-0" />
|
||||
<span className="text-xs font-medium text-brand-500 whitespace-nowrap">
|
||||
Creating spec
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<BoardSearchBar
|
||||
searchQuery={searchQuery}
|
||||
onSearchChange={setSearchQuery}
|
||||
isCreatingSpec={isCreatingSpec}
|
||||
creatingSpecProjectPath={creatingSpecProjectPath}
|
||||
currentProjectPath={currentProject?.path}
|
||||
/>
|
||||
|
||||
{/* Board Background & Detail Level Controls */}
|
||||
{isMounted && (
|
||||
|
||||
89
apps/app/src/components/views/board-view/BoardSearchBar.tsx
Normal file
89
apps/app/src/components/views/board-view/BoardSearchBar.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
"use client";
|
||||
|
||||
import { useRef, useEffect } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Search, X, Loader2 } from "lucide-react";
|
||||
|
||||
interface BoardSearchBarProps {
|
||||
searchQuery: string;
|
||||
onSearchChange: (query: string) => void;
|
||||
isCreatingSpec: boolean;
|
||||
creatingSpecProjectPath?: string;
|
||||
currentProjectPath?: string;
|
||||
}
|
||||
|
||||
export function BoardSearchBar({
|
||||
searchQuery,
|
||||
onSearchChange,
|
||||
isCreatingSpec,
|
||||
creatingSpecProjectPath,
|
||||
currentProjectPath,
|
||||
}: BoardSearchBarProps) {
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// Focus search input when "/" is pressed
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Only focus if not typing in an input/textarea
|
||||
if (
|
||||
e.key === "/" &&
|
||||
!(e.target instanceof HTMLInputElement) &&
|
||||
!(e.target instanceof HTMLTextAreaElement)
|
||||
) {
|
||||
e.preventDefault();
|
||||
searchInputRef.current?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative max-w-md flex-1 flex items-center gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground pointer-events-none" />
|
||||
<Input
|
||||
ref={searchInputRef}
|
||||
type="text"
|
||||
placeholder="Search features by keyword..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
className="pl-9 pr-12 border-border"
|
||||
data-testid="kanban-search-input"
|
||||
/>
|
||||
{searchQuery ? (
|
||||
<button
|
||||
onClick={() => onSearchChange("")}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded-sm hover:bg-accent text-muted-foreground hover:text-foreground transition-colors"
|
||||
data-testid="kanban-search-clear"
|
||||
aria-label="Clear search"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
) : (
|
||||
<span
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-brand-500/10 border border-brand-500/30 text-brand-400/70"
|
||||
data-testid="kanban-search-hotkey"
|
||||
>
|
||||
/
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Spec Creation Loading Badge */}
|
||||
{isCreatingSpec &&
|
||||
currentProjectPath === creatingSpecProjectPath && (
|
||||
<div
|
||||
className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-brand-500/10 border border-brand-500/20 shrink-0"
|
||||
title="Creating App Specification"
|
||||
data-testid="spec-creation-badge"
|
||||
>
|
||||
<Loader2 className="w-3 h-3 animate-spin text-brand-500 shrink-0" />
|
||||
<span className="text-xs font-medium text-brand-500 whitespace-nowrap">
|
||||
Creating spec
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user