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