197 lines
6.0 KiB
TypeScript
197 lines
6.0 KiB
TypeScript
|
"use client"
|
||
|
|
||
|
import React, { FormEvent, useRef, useState } from "react"
|
||
|
import {
|
||
|
MagnifyingGlassIcon as SearchIcon,
|
||
|
FunnelIcon as FilterIcon,
|
||
|
ChevronDownIcon,
|
||
|
ChevronRightIcon
|
||
|
} from "@heroicons/react/24/outline" // Assuming usage of Heroicons for icons
|
||
|
import { formatDistanceToNow } from "date-fns" // date-fns library used for date formatting
|
||
|
import { flushSync } from "react-dom"
|
||
|
|
||
|
// Utility function to format dates
|
||
|
const formatDate = (date: string | Date) => {
|
||
|
const distance = formatDistanceToNow(new Date(date), { addSuffix: true })
|
||
|
return distance
|
||
|
.replace(/less than a minute?/, "<1m")
|
||
|
.replace(/ seconds?/, "s")
|
||
|
.replace(/ minutes?/, "m")
|
||
|
.replace(/ hours?/, "h")
|
||
|
.replace(/ days?/, "d")
|
||
|
.replace(/ weeks?/, "w")
|
||
|
}
|
||
|
|
||
|
const SearchBar = () => {
|
||
|
const inputRef = useRef<HTMLInputElement>()
|
||
|
const [query, setQuery] = useState("")
|
||
|
const [isLoading, setIsLoading] = useState(false)
|
||
|
const [activeInput, setActiveInput] = useState(true)
|
||
|
const [results, setResults] = useState<
|
||
|
{
|
||
|
id: number
|
||
|
timestamp: Date
|
||
|
title: string
|
||
|
description: string
|
||
|
}[]
|
||
|
>([])
|
||
|
|
||
|
const handleSearch = async (event: FormEvent<HTMLFormElement>) => {
|
||
|
event.preventDefault()
|
||
|
setIsLoading(true)
|
||
|
// Perform search and update results state
|
||
|
// Assume fetchResults is a function that fetches search results
|
||
|
// const searchResults = await fetchResults(query);
|
||
|
// Mock the search results
|
||
|
const searchResults = [
|
||
|
{
|
||
|
id: 1,
|
||
|
timestamp: new Date(),
|
||
|
title: "Mock Title 1",
|
||
|
description: "Mock Description 1"
|
||
|
},
|
||
|
{
|
||
|
id: 2,
|
||
|
timestamp: new Date(),
|
||
|
title: "Mock Title 2",
|
||
|
description: "Mock Description 2"
|
||
|
}
|
||
|
]
|
||
|
setResults(searchResults)
|
||
|
setIsLoading(false)
|
||
|
setActiveInput(false)
|
||
|
}
|
||
|
|
||
|
return (
|
||
|
<div
|
||
|
className={`w-full p-4 border-2 ${
|
||
|
results.length > 0 ? "border-sky-300 bg-gray-950" : "border-primary"
|
||
|
}`}
|
||
|
>
|
||
|
<form className={`flex items-center text-lg`} onSubmit={handleSearch}>
|
||
|
{isLoading || results.length > 0 ? (
|
||
|
<span className="text-white mr-2">Searching for</span>
|
||
|
) : null}
|
||
|
{activeInput ? (
|
||
|
<fieldset
|
||
|
className={`block w-full p-0 h-auto flex-1 overflow-hidden`}
|
||
|
>
|
||
|
{results.length > 0 ? (
|
||
|
<span className="text-blue-300 underline-offset-4 underline mr-[-0.5px]">
|
||
|
{'"'}
|
||
|
</span>
|
||
|
) : (
|
||
|
""
|
||
|
)}
|
||
|
<input
|
||
|
ref={(element) => {
|
||
|
if (element) {
|
||
|
inputRef.current = element
|
||
|
}
|
||
|
}}
|
||
|
className={`flex-grow inline bg-transparent text-white placeholder-gray-400 outline-none ring-none ${
|
||
|
results.length > 0
|
||
|
? `text-blue-300 p-0 underline underline-offset-4`
|
||
|
: "w-full p-2"
|
||
|
}`}
|
||
|
placeholder={
|
||
|
results.length === 0
|
||
|
? "Search for web3 news from the community"
|
||
|
: undefined
|
||
|
}
|
||
|
value={query}
|
||
|
size={query ? query.length : 1}
|
||
|
style={
|
||
|
query
|
||
|
? {
|
||
|
width: `calc(${query.length}ch+2px)`
|
||
|
}
|
||
|
: undefined
|
||
|
}
|
||
|
onChange={(e) => setQuery(e.target.value)}
|
||
|
/>
|
||
|
{results.length > 0 ? (
|
||
|
<span className="text-blue-300 underline-offset-4 underline ml-[-5.5px]">
|
||
|
{'"'}
|
||
|
</span>
|
||
|
) : (
|
||
|
""
|
||
|
)}
|
||
|
</fieldset>
|
||
|
) : (
|
||
|
<span
|
||
|
className="block w-full flex-1 text-blue-300"
|
||
|
onClick={() => {
|
||
|
flushSync(() => {
|
||
|
setActiveInput(true)
|
||
|
})
|
||
|
inputRef.current?.focus()
|
||
|
}}
|
||
|
>
|
||
|
{'"'}
|
||
|
{query}
|
||
|
{'"'}
|
||
|
</span>
|
||
|
)}
|
||
|
{isLoading ? (
|
||
|
// Shadcn Loading component placeholder
|
||
|
<LoadingComponent />
|
||
|
) : (
|
||
|
<div className="justify-self-end w-[220px] flex">
|
||
|
{/* Dropdown component should be here */}
|
||
|
<div className="uppercase text-white text-[12px] mx-3 font-semibold">
|
||
|
<span>All Sites</span>
|
||
|
<ChevronDownIcon className="w-4 h-4 inline-block ml-2" />
|
||
|
</div>
|
||
|
{/* Dropdown component should be here */}
|
||
|
<div className="uppercase text-white text-[12px] mx-3 font-semibold">
|
||
|
<span>All Times</span>
|
||
|
<ChevronDownIcon className="w-4 h-4 inline-block ml-2" />
|
||
|
</div>
|
||
|
</div>
|
||
|
)}
|
||
|
</form>
|
||
|
|
||
|
{results.length > 0 && (
|
||
|
<>
|
||
|
<hr className="my-4 border-1" />
|
||
|
{results.map((item) => (
|
||
|
<div
|
||
|
key={item.id}
|
||
|
className="flex cursor-pointer flex-row items-center space-x-5 my-2 py-2 px-4 hover:bg-gray-800 rounded-md"
|
||
|
>
|
||
|
<span className="text-sm text-gray-400">
|
||
|
{formatDate(item.timestamp)}
|
||
|
</span>
|
||
|
<h3 className="text-md font-semibold text-white">{item.title}</h3>
|
||
|
</div>
|
||
|
))}
|
||
|
<div className="mt-4 flex justify-center">
|
||
|
<button className="bg-secondary w-full py-7 text-white hover:bg-teal-800 transition-colors">
|
||
|
{results.length}+ search results for <span className="text-blue-300">{query}</span>
|
||
|
<ChevronRightIcon className="w-5 h-5 inline ml-2 -mt-[2px]" />
|
||
|
</button>
|
||
|
</div>
|
||
|
</>
|
||
|
)}
|
||
|
</div>
|
||
|
)
|
||
|
}
|
||
|
|
||
|
// Placeholder components for Shadcn
|
||
|
const LoadingComponent = () => {
|
||
|
// Replace with actual Shadcn Loading component
|
||
|
return <div>Loading...</div>
|
||
|
}
|
||
|
|
||
|
const FilterComponent = () => {
|
||
|
// Replace with actual Shadcn Filter component
|
||
|
return (
|
||
|
<button>
|
||
|
<FilterIcon className="h-5 w-5 text-white" />
|
||
|
</button>
|
||
|
)
|
||
|
}
|
||
|
|
||
|
export default SearchBar
|