feat: Refactored SearchAutocomplete from heroSize boolean to variant st…
- "frontend/src/components/SearchAutocomplete.tsx" - "frontend/src/App.tsx" - "frontend/src/App.css" - "frontend/src/pages/Home.tsx" - "frontend/src/pages/SearchResults.tsx" GSD-Task: S03/T01
This commit is contained in:
parent
fa1fc82d5a
commit
fea0afdec0
5 changed files with 95 additions and 8 deletions
|
|
@ -1042,6 +1042,53 @@ a.app-footer__repo:hover {
|
|||
border-radius: 0.625rem;
|
||||
}
|
||||
|
||||
/* ── Nav search variant ───────────────────────────────────────────────────── */
|
||||
|
||||
.search-container--nav {
|
||||
position: relative;
|
||||
max-width: 16rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search-form--nav {
|
||||
gap: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-input--nav {
|
||||
padding: 0.375rem 2.75rem 0.375rem 0.75rem;
|
||||
font-size: 0.8125rem;
|
||||
border-radius: 0.375rem;
|
||||
background: var(--color-bg-input);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.search-input--nav::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.search-nav__shortcut {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 0.625rem;
|
||||
font-family: inherit;
|
||||
color: var(--color-text-muted);
|
||||
background: var(--color-bg-page);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.0625rem 0.3125rem;
|
||||
line-height: 1.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.search-container--nav .typeahead-dropdown {
|
||||
z-index: 200;
|
||||
min-width: 20rem;
|
||||
}
|
||||
|
||||
.btn--search {
|
||||
background: var(--color-btn-search-bg);
|
||||
color: var(--color-btn-search-text);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Link, Navigate, Route, Routes } from "react-router-dom";
|
||||
import { Link, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom";
|
||||
import Home from "./pages/Home";
|
||||
import SearchResults from "./pages/SearchResults";
|
||||
import TechniquePage from "./pages/TechniquePage";
|
||||
|
|
@ -11,14 +11,27 @@ import AdminPipeline from "./pages/AdminPipeline";
|
|||
import About from "./pages/About";
|
||||
import AdminDropdown from "./components/AdminDropdown";
|
||||
import AppFooter from "./components/AppFooter";
|
||||
import SearchAutocomplete from "./components/SearchAutocomplete";
|
||||
|
||||
export default function App() {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const showNavSearch = location.pathname !== "/";
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<header className="app-header">
|
||||
<Link to="/" className="app-header__brand">
|
||||
<h1>Chrysopedia</h1>
|
||||
</Link>
|
||||
{showNavSearch && (
|
||||
<SearchAutocomplete
|
||||
variant="nav"
|
||||
globalShortcut
|
||||
placeholder="Search… ⌘K"
|
||||
onSearch={(q) => navigate(`/search?q=${encodeURIComponent(q)}`)}
|
||||
/>
|
||||
)}
|
||||
<div className="app-header__right">
|
||||
<nav className="app-nav">
|
||||
<Link to="/">Home</Link>
|
||||
|
|
|
|||
|
|
@ -19,18 +19,26 @@ import {
|
|||
interface SearchAutocompleteProps {
|
||||
onSearch: (query: string) => void;
|
||||
placeholder?: string;
|
||||
/** @deprecated Use variant="hero" instead */
|
||||
heroSize?: boolean;
|
||||
variant?: 'hero' | 'inline' | 'nav';
|
||||
initialQuery?: string;
|
||||
autoFocus?: boolean;
|
||||
/** When true, Cmd+K / Ctrl+K focuses the input globally */
|
||||
globalShortcut?: boolean;
|
||||
}
|
||||
|
||||
export default function SearchAutocomplete({
|
||||
onSearch,
|
||||
placeholder = "Search techniques…",
|
||||
heroSize = false,
|
||||
variant: variantProp,
|
||||
initialQuery = "",
|
||||
autoFocus = false,
|
||||
globalShortcut = false,
|
||||
}: SearchAutocompleteProps) {
|
||||
// Resolve variant: explicit prop wins, then legacy heroSize, then default 'inline'
|
||||
const variant = variantProp ?? (heroSize ? 'hero' : 'inline');
|
||||
const [query, setQuery] = useState(initialQuery);
|
||||
const [searchResults, setSearchResults] = useState<SearchResultItem[]>([]);
|
||||
const [popularSuggestions, setPopularSuggestions] = useState<SuggestionItem[]>([]);
|
||||
|
|
@ -64,6 +72,19 @@ export default function SearchAutocomplete({
|
|||
if (autoFocus) inputRef.current?.focus();
|
||||
}, [autoFocus]);
|
||||
|
||||
// Global Cmd+K / Ctrl+K shortcut to focus input
|
||||
useEffect(() => {
|
||||
if (!globalShortcut) return;
|
||||
function handleGlobalKey(e: KeyboardEvent) {
|
||||
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}
|
||||
document.addEventListener("keydown", handleGlobalKey);
|
||||
return () => document.removeEventListener("keydown", handleGlobalKey);
|
||||
}, [globalShortcut]);
|
||||
|
||||
// Close dropdown on outside click
|
||||
useEffect(() => {
|
||||
function handleClick(e: MouseEvent) {
|
||||
|
|
@ -145,15 +166,15 @@ export default function SearchAutocomplete({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="search-container" ref={dropdownRef}>
|
||||
<div className={`search-container${variant === 'nav' ? ' search-container--nav' : ''}`} ref={dropdownRef}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className={`search-form ${heroSize ? "search-form--hero" : "search-form--inline"}`}
|
||||
className={`search-form search-form--${variant}`}
|
||||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="search"
|
||||
className={`search-input ${heroSize ? "search-input--hero" : "search-input--inline"}`}
|
||||
className={`search-input search-input--${variant}`}
|
||||
placeholder={placeholder}
|
||||
value={query}
|
||||
onChange={(e) => handleInputChange(e.target.value)}
|
||||
|
|
@ -161,9 +182,14 @@ export default function SearchAutocomplete({
|
|||
onKeyDown={handleKeyDown}
|
||||
aria-label="Search techniques"
|
||||
/>
|
||||
{variant !== 'nav' && (
|
||||
<button type="submit" className="btn btn--search">
|
||||
Search
|
||||
</button>
|
||||
)}
|
||||
{variant === 'nav' && (
|
||||
<kbd className="search-nav__shortcut">⌘K</kbd>
|
||||
)}
|
||||
</form>
|
||||
|
||||
{showDropdown && (showPopular || showSearch) && (
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export default function Home() {
|
|||
</p>
|
||||
|
||||
<SearchAutocomplete
|
||||
heroSize
|
||||
variant="hero"
|
||||
autoFocus
|
||||
onSearch={(q) => navigate(`/search?q=${encodeURIComponent(q)}`)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export default function SearchResults() {
|
|||
<div className="search-results-page">
|
||||
{/* Inline search bar */}
|
||||
<SearchAutocomplete
|
||||
variant="inline"
|
||||
initialQuery={q}
|
||||
onSearch={(newQ) =>
|
||||
navigate(`/search?q=${encodeURIComponent(newQ)}`, { replace: true })
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue