11"use client" ;
22
33import { SearchForm } from "@techsio/ui-kit/molecules/search-form" ;
4- import { useRouter } from "next/navigation" ;
5- import {
6- useId ,
7- useMemo ,
8- useState ,
9- type FocusEvent ,
10- type FormEvent ,
11- type KeyboardEvent ,
12- type MouseEvent ,
13- } from "react" ;
14- import {
15- SEARCH_AUTOCOMPLETE_MIN_QUERY_LENGTH ,
16- type SearchAutocompleteSuggestion ,
17- } from "@/lib/search-autocomplete/search-autocomplete-types" ;
18- import {
19- getSearchAutocompleteOptionId ,
20- SearchAutocompletePanel ,
21- } from "./search-autocomplete-panel" ;
22- import {
23- clampSearchAutocompleteIndex ,
24- createSearchAutocompleteSections ,
25- } from "./search-autocomplete-sections" ;
26- import { useSearchAutocomplete } from "./use-search-autocomplete" ;
4+ import type { FormEvent } from "react" ;
5+ import { SEARCH_AUTOCOMPLETE_MAX_QUERY_LENGTH } from "@/lib/search-autocomplete/search-autocomplete-types" ;
6+ import { SearchAutocompletePanel } from "./search-autocomplete-panel" ;
7+ import { useSearchAutocompleteController } from "./use-search-autocomplete-controller" ;
278
289type SearchAutocompleteProps = {
10+ countryCode ?: string ;
2911 currencyCode : string ;
3012 onSubmit : ( event : FormEvent < HTMLFormElement > ) => void ;
13+ regionId ?: string ;
3114 variant : "desktop" | "mobile" ;
3215} ;
3316
3417export function SearchAutocomplete ( {
18+ countryCode,
3519 currencyCode,
3620 onSubmit,
21+ regionId,
3722 variant,
3823} : SearchAutocompleteProps ) {
39- const router = useRouter ( ) ;
40- const generatedId = useId ( ) ;
41- const panelId = `${ generatedId } -search-autocomplete` ;
4224 const isMobile = variant === "mobile" ;
43- const [ value , setValue ] = useState ( "" ) ;
44- const [ isFocused , setIsFocused ] = useState ( false ) ;
45- const [ activeIndex , setActiveIndex ] = useState ( - 1 ) ;
46- const autocomplete = useSearchAutocomplete ( { query : value , currencyCode } ) ;
47- const normalizedQuery = value . trim ( ) ;
48- const sections = useMemo (
49- ( ) => createSearchAutocompleteSections ( autocomplete . data ) ,
50- [ autocomplete . data ] ,
51- ) ;
52- const flatItems = useMemo (
53- ( ) => sections . flatMap ( ( section ) => section . items ) ,
54- [ sections ] ,
55- ) ;
56- const activeItem = flatItems [ activeIndex ] ?? null ;
57- const shouldShowPanel =
58- isFocused &&
59- normalizedQuery . length >= SEARCH_AUTOCOMPLETE_MIN_QUERY_LENGTH &&
60- autocomplete . status !== "idle" ;
61- const activeItemId =
62- shouldShowPanel && activeItem
63- ? getSearchAutocompleteOptionId ( panelId , activeItem )
64- : undefined ;
65-
66- const closePanel = ( ) => {
67- setIsFocused ( false ) ;
68- setActiveIndex ( - 1 ) ;
69- } ;
70-
71- const handleValueChange = ( nextValue : string ) => {
72- setValue ( nextValue ) ;
73- setActiveIndex ( - 1 ) ;
74- } ;
75-
76- const handleBlur = ( event : FocusEvent < HTMLDivElement > ) => {
77- const nextFocusedElement = event . relatedTarget ;
78- if (
79- nextFocusedElement instanceof Node &&
80- event . currentTarget . contains ( nextFocusedElement )
81- ) {
82- return ;
83- }
84-
85- closePanel ( ) ;
86- } ;
87-
88- const handleKeyDown = ( event : KeyboardEvent < HTMLInputElement > ) => {
89- if ( event . key === "Escape" ) {
90- closePanel ( ) ;
91- return ;
92- }
93-
94- if ( ! shouldShowPanel || flatItems . length === 0 ) {
95- return ;
96- }
97-
98- if ( event . key === "ArrowDown" || event . key === "ArrowUp" ) {
99- event . preventDefault ( ) ;
100- setActiveIndex ( ( currentIndex ) =>
101- clampSearchAutocompleteIndex (
102- event . key === "ArrowDown" ? currentIndex + 1 : currentIndex - 1 ,
103- flatItems . length ,
104- ) ,
105- ) ;
106- return ;
107- }
108-
109- if ( event . key === "Enter" && activeItem ) {
110- event . preventDefault ( ) ;
111- router . push ( activeItem . href ) ;
112- closePanel ( ) ;
113- }
114- } ;
115-
116- const handleItemMouseEnter = ( item : SearchAutocompleteSuggestion ) => {
117- setActiveIndex (
118- flatItems . findIndex (
119- ( candidate ) => candidate . id === item . id && candidate . type === item . type ,
120- ) ,
121- ) ;
122- } ;
123-
124- const handlePanelMouseDown = ( event : MouseEvent < HTMLDivElement > ) => {
125- event . preventDefault ( ) ;
126- } ;
25+ const controller = useSearchAutocompleteController ( {
26+ countryCode,
27+ currencyCode,
28+ onSubmit,
29+ regionId,
30+ } ) ;
12731
12832 return (
129- < div className = "relative w-full" onBlur = { handleBlur } >
33+ < div className = "relative w-full" onBlur = { controller . handleBlur } >
13034 < SearchForm
13135 className = "w-full"
132- onSubmit = { onSubmit }
133- onValueChange = { handleValueChange }
134- value = { value }
36+ onSubmit = { controller . handleSubmit }
37+ onValueChange = { controller . handleValueChange }
38+ value = { controller . value }
13539 >
13640 < SearchForm . Control className = "h-search-form border border-border-search bg-fill-secondary" >
13741 < SearchForm . Input
138- aria-activedescendant = { activeItemId }
42+ aria-activedescendant = { controller . activeItemId }
13943 aria-autocomplete = "list"
140- aria-controls = { shouldShowPanel ? panelId : undefined }
141- aria-expanded = { shouldShowPanel }
44+ aria-controls = {
45+ controller . hasItems ? controller . panelId : undefined
46+ }
47+ aria-expanded = { controller . shouldShowPanel }
14248 aria-haspopup = "listbox"
14349 className = { `${ isMobile ? "px-350 text-sm" : "px-400" } h-full font-verdana` }
50+ maxLength = { SEARCH_AUTOCOMPLETE_MAX_QUERY_LENGTH }
14451 name = "q"
145- onFocus = { ( ) => setIsFocused ( true ) }
146- onKeyDown = { handleKeyDown }
52+ onFocus = { controller . handleFocus }
53+ onKeyDown = { controller . handleKeyDown }
14754 placeholder = "Napíšte, čo hľadáte..."
14855 role = "combobox"
14956 />
@@ -159,16 +66,16 @@ export function SearchAutocomplete({
15966 />
16067 </ SearchForm . Control >
16168
162- { shouldShowPanel ? (
69+ { controller . shouldShowPanel ? (
16370 < SearchAutocompletePanel
164- activeItemId = { activeItemId }
165- id = { panelId }
166- onItemClick = { closePanel }
167- onItemMouseEnter = { handleItemMouseEnter }
168- onMouseDown = { handlePanelMouseDown }
169- query = { normalizedQuery }
170- sections = { sections }
171- status = { autocomplete . status }
71+ activeItemId = { controller . activeItemId }
72+ id = { controller . panelId }
73+ onItemClick = { controller . closePanel }
74+ onItemMouseEnter = { controller . handleItemMouseEnter }
75+ onMouseDown = { controller . handlePanelMouseDown }
76+ query = { controller . normalizedQuery }
77+ sections = { controller . sections }
78+ status = { controller . status }
17279 />
17380 ) : null }
17481 </ SearchForm >
0 commit comments