Browse Source
🔧 Refactor component for better usability
🔧 Refactor component for better usability
🐞 Fix issue with recent searches 🚀 Implement new features 👌 Improve code readability and structuremain
John Doe
1 year ago
1 changed files with 171 additions and 0 deletions
@ -0,0 +1,171 @@ |
|||
"use client"; |
|||
|
|||
import { ClockIcon, MapPinIcon } from "@heroicons/react/24/outline"; |
|||
import React, { useState, useRef, useEffect, FC } from "react"; |
|||
import ClearDataButton from "./ClearDataButton"; |
|||
|
|||
export interface LocationInputProps { |
|||
placeHolder?: string; |
|||
desc?: string; |
|||
className?: string; |
|||
divHideVerticalLineClass?: string; |
|||
autoFocus?: boolean; |
|||
} |
|||
|
|||
const LocationInput: FC<LocationInputProps> = ({ |
|||
autoFocus = false, |
|||
placeHolder = "Location", |
|||
desc = "Where are you going?", |
|||
className = "nc-flex-1.5", |
|||
divHideVerticalLineClass = "left-10 -right-0.5", |
|||
}) => { |
|||
const containerRef = useRef<HTMLDivElement>(null); |
|||
const inputRef = useRef<HTMLInputElement>(null); |
|||
|
|||
const [value, setValue] = useState(""); |
|||
const [showPopover, setShowPopover] = useState(autoFocus); |
|||
|
|||
useEffect(() => { |
|||
setShowPopover(autoFocus); |
|||
}, [autoFocus]); |
|||
|
|||
useEffect(() => { |
|||
if (eventClickOutsideDiv) { |
|||
document.removeEventListener("click", eventClickOutsideDiv); |
|||
} |
|||
showPopover && document.addEventListener("click", eventClickOutsideDiv); |
|||
return () => { |
|||
document.removeEventListener("click", eventClickOutsideDiv); |
|||
}; |
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|||
}, [showPopover]); |
|||
|
|||
useEffect(() => { |
|||
if (showPopover && inputRef.current) { |
|||
inputRef.current.focus(); |
|||
} |
|||
}, [showPopover]); |
|||
|
|||
const eventClickOutsideDiv = (event: MouseEvent) => { |
|||
if (!containerRef.current) return; |
|||
// CLICK IN_SIDE
|
|||
if (!showPopover || containerRef.current.contains(event.target as Node)) { |
|||
return; |
|||
} |
|||
// CLICK OUT_SIDE
|
|||
setShowPopover(false); |
|||
}; |
|||
|
|||
const handleSelectLocation = (item: string) => { |
|||
setValue(item); |
|||
setShowPopover(false); |
|||
}; |
|||
|
|||
const renderRecentSearches = () => { |
|||
return ( |
|||
<> |
|||
<h3 className="block mt-2 sm:mt-0 px-4 sm:px-8 font-semibold text-base sm:text-lg text-neutral-800 dark:text-neutral-100"> |
|||
Recent searches |
|||
</h3> |
|||
<div className="mt-2"> |
|||
{[ |
|||
"Hamptons, Suffolk County, NY", |
|||
"Las Vegas, NV, United States", |
|||
"Ueno, Taito, Tokyo", |
|||
"Ikebukuro, Toshima, Tokyo", |
|||
].map((item) => ( |
|||
<span |
|||
onClick={() => handleSelectLocation(item)} |
|||
key={item} |
|||
className="flex px-4 sm:px-8 items-center space-x-3 sm:space-x-4 py-4 hover:bg-neutral-100 dark:hover:bg-neutral-700 cursor-pointer" |
|||
> |
|||
<span className="block text-neutral-400"> |
|||
<ClockIcon className="h-4 sm:h-6 w-4 sm:w-6" /> |
|||
</span> |
|||
<span className=" block font-medium text-neutral-700 dark:text-neutral-200"> |
|||
{item} |
|||
</span> |
|||
</span> |
|||
))} |
|||
</div> |
|||
</> |
|||
); |
|||
}; |
|||
|
|||
const renderSearchValue = () => { |
|||
return ( |
|||
<> |
|||
{[ |
|||
"Ha Noi, Viet Nam", |
|||
"San Diego, CA", |
|||
"Humboldt Park, Chicago, IL", |
|||
"Bangor, Northern Ireland", |
|||
].map((item) => ( |
|||
<span |
|||
onClick={() => handleSelectLocation(item)} |
|||
key={item} |
|||
className="flex px-4 sm:px-8 items-center space-x-3 sm:space-x-4 py-4 hover:bg-neutral-100 dark:hover:bg-neutral-700 cursor-pointer" |
|||
> |
|||
<span className="block text-neutral-400"> |
|||
<ClockIcon className="h-4 w-4 sm:h-6 sm:w-6" /> |
|||
</span> |
|||
<span className="block font-medium text-neutral-700 dark:text-neutral-200"> |
|||
{item} |
|||
</span> |
|||
</span> |
|||
))} |
|||
</> |
|||
); |
|||
}; |
|||
|
|||
return ( |
|||
<div className={`relative flex ${className}`} ref={containerRef}> |
|||
<div |
|||
onClick={() => setShowPopover(true)} |
|||
className={`flex z-10 flex-1 relative [ nc-hero-field-padding ] flex-shrink-0 items-center space-x-3 cursor-pointer focus:outline-none text-left ${ |
|||
showPopover ? "nc-hero-field-focused" : "" |
|||
}`}
|
|||
> |
|||
<div className="text-neutral-300 dark:text-neutral-400"> |
|||
<MapPinIcon className="w-5 h-5 lg:w-7 lg:h-7" /> |
|||
</div> |
|||
<div className="flex-grow"> |
|||
<input |
|||
className={`block w-full bg-transparent border-none focus:ring-0 p-0 focus:outline-none focus:placeholder-neutral-300 xl:text-lg font-semibold placeholder-neutral-800 dark:placeholder-neutral-200 truncate`} |
|||
placeholder={placeHolder} |
|||
value={value} |
|||
autoFocus={showPopover} |
|||
onChange={(e) => { |
|||
setValue(e.currentTarget.value); |
|||
}} |
|||
ref={inputRef} |
|||
/> |
|||
<span className="block mt-0.5 text-sm text-neutral-400 font-light "> |
|||
<span className="line-clamp-1">{!!value ? placeHolder : desc}</span> |
|||
</span> |
|||
{value && showPopover && ( |
|||
<ClearDataButton |
|||
onClick={() => { |
|||
setValue(""); |
|||
}} |
|||
/> |
|||
)} |
|||
</div> |
|||
</div> |
|||
|
|||
{showPopover && ( |
|||
<div |
|||
className={`h-8 absolute self-center top-1/2 -translate-y-1/2 z-0 bg-white dark:bg-neutral-800 ${divHideVerticalLineClass}`} |
|||
></div> |
|||
)} |
|||
|
|||
{showPopover && ( |
|||
<div className="absolute left-0 z-40 w-full min-w-[300px] sm:min-w-[500px] bg-white dark:bg-neutral-800 top-full mt-3 py-3 sm:py-6 rounded-3xl shadow-xl max-h-96 overflow-y-auto"> |
|||
{value ? renderSearchValue() : renderRecentSearches()} |
|||
</div> |
|||
)} |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default LocationInput; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue