Initial release v1.0.0
This commit is contained in:
94
frontend/features/search/components/TVSearchInput.tsx
Normal file
94
frontend/features/search/components/TVSearchInput.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { Search as SearchIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useIsTV } from "@/lib/tv-utils";
|
||||
|
||||
interface TVSearchInputProps {
|
||||
initialQuery?: string;
|
||||
onSearch: (query: string) => void;
|
||||
}
|
||||
|
||||
export function TVSearchInput({ initialQuery = "", onSearch }: TVSearchInputProps) {
|
||||
const isTV = useIsTV();
|
||||
const router = useRouter();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [query, setQuery] = useState(initialQuery);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
// Update query when initialQuery changes
|
||||
useEffect(() => {
|
||||
setQuery(initialQuery);
|
||||
}, [initialQuery]);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (query.trim()) {
|
||||
onSearch(query.trim());
|
||||
// Blur the input after search to return to D-pad navigation
|
||||
inputRef.current?.blur();
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
// On Enter, submit the search
|
||||
if (e.key === "Enter") {
|
||||
handleSubmit(e);
|
||||
}
|
||||
// On Escape, blur the input
|
||||
if (e.key === "Escape") {
|
||||
inputRef.current?.blur();
|
||||
}
|
||||
};
|
||||
|
||||
// Only render this component in TV mode
|
||||
if (!isTV) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-8" data-tv-section="tv-search">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="relative max-w-2xl">
|
||||
<SearchIcon className="absolute left-5 top-1/2 -translate-y-1/2 w-6 h-6 text-gray-400" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
placeholder="Press Enter to search..."
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
autoComplete="off"
|
||||
data-tv-card
|
||||
data-tv-card-index={0}
|
||||
tabIndex={0}
|
||||
className={`
|
||||
w-full h-16 pl-14 pr-6
|
||||
bg-[#1a1a1a]
|
||||
rounded-lg
|
||||
text-xl text-white
|
||||
placeholder-gray-500
|
||||
transition-all
|
||||
outline-none
|
||||
border-2
|
||||
${isFocused
|
||||
? "border-[#ecb200] bg-[#242424]"
|
||||
: "border-transparent hover:bg-[#242424]"
|
||||
}
|
||||
`}
|
||||
/>
|
||||
{query && (
|
||||
<div className="absolute right-5 top-1/2 -translate-y-1/2 text-sm text-gray-500">
|
||||
Press Enter to search
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user