Skip to content

Commit d683196

Browse files
committed
Seach escaping & click outside handle
1 parent 493126e commit d683196

File tree

1 file changed

+76
-30
lines changed

1 file changed

+76
-30
lines changed

src/app/components/ui/ukhsa/Search/Search.tsx

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22
import Link from 'next/link'
3-
import { useEffect, useState } from 'react'
3+
import { useEffect, useRef, useState } from 'react'
44
import { useDebounceValue } from 'usehooks-ts'
55

66
import { getPages } from '@/api/requests/cms/getPages'
@@ -26,6 +26,9 @@ export function Search({ label, placeholder }: SearchProps) {
2626
const [searchInputValue, setSearchInputValue] = useState('')
2727
const [debouncedSearchValue] = useDebounceValue(searchInputValue, DEBOUNCE_MILLISECONDS)
2828
const [searchResults, setSearchResults] = useState<SearchResult[] | undefined>([])
29+
const [isDropdownOpen, setIsDropdownOpen] = useState(false)
30+
const searchContainerRef = useRef<HTMLDivElement>(null)
31+
const searchInputRef = useRef<HTMLInputElement>(null)
2932

3033
const getSearchResults = async ({ query }: { query: string }) => {
3134
const pages = await getPages({ limit: limit.toString(), search: query })
@@ -40,41 +43,84 @@ export function Search({ label, placeholder }: SearchProps) {
4043
}
4144
}, [debouncedSearchValue])
4245

46+
useEffect(() => {
47+
const handleClickOutside = (event: MouseEvent) => {
48+
if (searchContainerRef.current && !searchContainerRef.current.contains(event.target as Node)) {
49+
setIsDropdownOpen(false)
50+
}
51+
}
52+
53+
document.addEventListener('mousedown', handleClickOutside)
54+
55+
return () => {
56+
document.removeEventListener('mousedown', handleClickOutside)
57+
}
58+
}, [])
59+
4360
return (
4461
<form method="GET" aria-label={label}>
45-
{/* <div className="govuk-form-group mb-0 mt-2"> */}
46-
<div className="govuk-form-group">
62+
<div className="govuk-form-group relative" ref={searchContainerRef}>
4763
<label className="govuk-label" htmlFor="search" hidden={true}>
4864
{placeholder}
4965
</label>
50-
<input
51-
className="govuk-input govuk-!-font-size-16"
52-
id="search"
53-
name="search"
54-
type="text"
55-
placeholder={placeholder}
56-
value={searchInputValue}
57-
onChange={(event) => {
58-
setSearchInputValue(event.currentTarget.value)
59-
}}
60-
/>
61-
<div className="absolute z-[1000] max-h-[200px] overflow-y-auto bg-white shadow-[0px_15px_15px_0px_rgba(0,0,0,0.35)] sm:w-5/12">
62-
<ul>
63-
{searchResults?.map(({ title, meta: { html_url } }, i) => (
64-
<li
65-
key={`result-${i}`}
66-
value="{i}"
67-
className="govuk-!-margin-left-2 govuk-!-margin-right-2 border-b shadow-[0_1px_0_#929191]"
68-
>
69-
<Link className="govuk-link group block focus:border-b-0 focus:outline-none" href={html_url || ''}>
70-
<div className="size-full border-2 border-transparent p-3 group-focus:border-black group-focus:bg-yellow">
71-
{title}
72-
</div>
73-
</Link>
74-
</li>
75-
))}
76-
</ul>
66+
<div className="relative">
67+
<input
68+
ref={searchInputRef}
69+
className="govuk-input govuk-!-font-size-16 govuk-!-width-full pr-[2.5rem]"
70+
id="search"
71+
name="search"
72+
type="text"
73+
placeholder={placeholder}
74+
value={searchInputValue}
75+
onFocus={() => {
76+
setIsDropdownOpen(true)
77+
}}
78+
onKeyDown={(event) => {
79+
if (event.key === 'Escape') {
80+
setIsDropdownOpen(false)
81+
}
82+
}}
83+
onChange={(event) => {
84+
setSearchInputValue(event.currentTarget.value)
85+
setIsDropdownOpen(true)
86+
}}
87+
/>
88+
{searchInputValue && (
89+
<button
90+
type="button"
91+
aria-label="Clear search"
92+
className="absolute inset-y-0 right-0 mt-[2px] flex h-[36px] items-center justify-center px-3 text-black"
93+
style={{ fontSize: '1.5rem', lineHeight: 1 }}
94+
onClick={() => {
95+
setSearchInputValue('')
96+
setSearchResults([])
97+
setIsDropdownOpen(false)
98+
searchInputRef.current?.focus()
99+
}}
100+
>
101+
&times;
102+
</button>
103+
)}
77104
</div>
105+
{isDropdownOpen && searchResults && searchResults.length > 0 && (
106+
<div className="absolute z-[1000] max-h-[200px] w-full overflow-y-auto bg-white shadow-[0px_15px_15px_0px_rgba(0,0,0,0.35)]">
107+
<ul>
108+
{searchResults.map(({ title, meta: { html_url } }, i) => (
109+
<li
110+
key={`result-${i}`}
111+
value="{i}"
112+
className="govuk-!-margin-left-2 govuk-!-margin-right-2 border-b shadow-[0_1px_0_#929191]"
113+
>
114+
<Link className="govuk-link group block focus:border-b-0 focus:outline-none" href={html_url || ''}>
115+
<div className="size-full border-2 border-transparent p-3 group-focus:border-black group-focus:bg-yellow">
116+
{title}
117+
</div>
118+
</Link>
119+
</li>
120+
))}
121+
</ul>
122+
</div>
123+
)}
78124
<noscript>
79125
<style>{'input#search {display: none; }'}</style>
80126
</noscript>

0 commit comments

Comments
 (0)