Browse Source
* add LocationInput component to allow users to input location
* add LocationInput component to allow users to input location
* add safety guidelines to commit messagemain
John Doe
1 year ago
1 changed files with 157 additions and 0 deletions
@ -0,0 +1,157 @@ |
|||||
|
"use client"; |
||||
|
|
||||
|
import React, { useState } from "react"; |
||||
|
import { FC } from "react"; |
||||
|
import { useEffect } from "react"; |
||||
|
import ClearDataButton from "./ClearDataButton"; |
||||
|
import { useRef } from "react"; |
||||
|
import useOutsideAlerter from "@/hooks/useOutsideAlerter"; |
||||
|
import { MapPinIcon } from "@heroicons/react/24/outline"; |
||||
|
|
||||
|
export interface LocationInputProps { |
||||
|
onInputDone?: (value: string) => void; |
||||
|
placeHolder?: string; |
||||
|
desc?: string; |
||||
|
className?: string; |
||||
|
divHideVerticalLineClass?: string; |
||||
|
autoFocus?: boolean; |
||||
|
} |
||||
|
|
||||
|
const LocationInput: FC<LocationInputProps> = ({ |
||||
|
autoFocus = false, |
||||
|
onInputDone, |
||||
|
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); |
||||
|
if (autoFocus && !!inputRef.current) { |
||||
|
setTimeout(() => { |
||||
|
inputRef.current && inputRef.current.focus(); |
||||
|
}, 200); |
||||
|
} |
||||
|
}, [autoFocus]); |
||||
|
|
||||
|
useOutsideAlerter(containerRef, () => { |
||||
|
setShowPopover(false); |
||||
|
}); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (showPopover && inputRef.current) { |
||||
|
inputRef.current.focus(); |
||||
|
} |
||||
|
}, [showPopover]); |
||||
|
|
||||
|
const handleSelectLocation = (item: string) => { |
||||
|
setValue(item); |
||||
|
onInputDone && onInputDone(item); |
||||
|
setShowPopover(false); |
||||
|
}; |
||||
|
|
||||
|
const renderRecentSearches = () => { |
||||
|
return ( |
||||
|
<> |
||||
|
<h3 className="block mt-2 sm:mt-0 px-4 sm:px-8 font-semibold text-base 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-6 items-center space-x-3 py-4 hover:bg-neutral-100 dark:hover:bg-neutral-700 cursor-pointer" |
||||
|
> |
||||
|
<span className="block text-neutral-400"> |
||||
|
<MapPinIcon className="h-4 w-4 sm:h-6 sm:w-6" /> |
||||
|
</span> |
||||
|
<span className=" block 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-6 items-center space-x-3 py-4 hover:bg-neutral-100 dark:hover:bg-neutral-700 cursor-pointer" |
||||
|
> |
||||
|
<span className="block text-neutral-400"> |
||||
|
<MapPinIcon className="h-4 w-4 sm:h-6 sm:w-6" /> |
||||
|
</span> |
||||
|
<span className="block text-neutral-700 dark:text-neutral-200"> |
||||
|
{item} |
||||
|
</span> |
||||
|
</span> |
||||
|
))} |
||||
|
</> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<div className={`relative flex ${className}`} ref={containerRef}> |
||||
|
<div |
||||
|
onClick={() => setShowPopover(true)} |
||||
|
className={`flex flex-1 relative z-10 [ nc-hero-field-padding--small ] flex-shrink-0 items-center space-x-3 cursor-pointer focus:outline-none text-left ${ |
||||
|
showPopover ? "nc-hero-field-focused--2" : "" |
||||
|
}`}
|
||||
|
> |
||||
|
<div className="flex-1"> |
||||
|
<input |
||||
|
className={`block w-full bg-transparent border-none focus:ring-0 p-0 focus:outline-none focus:placeholder-neutral-400 xl:text-base 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-[400px] bg-white dark:bg-neutral-800 top-full mt-3 py-3 sm:py-5 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