mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-11 03:24:16 +00:00
added Searcher
component
This commit is contained in:
parent
7d4149cb2c
commit
5a314bb3a7
157
packages/app/src/components/Searcher/index.jsx
Normal file
157
packages/app/src/components/Searcher/index.jsx
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
import classnames from "classnames"
|
||||||
|
|
||||||
|
import { UserPreview } from "components"
|
||||||
|
import { Icons, createIconRender } from "components/Icons"
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
const ResultRenders = {
|
||||||
|
users: (props) => {
|
||||||
|
const { item, onClick } = props
|
||||||
|
|
||||||
|
return <div className="suggestion">
|
||||||
|
<UserPreview onClick={onClick} user={item} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResultsTypeDecorators = {
|
||||||
|
users: {
|
||||||
|
icon: "Users",
|
||||||
|
label: "Users"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Results = (props) => {
|
||||||
|
let { results } = props
|
||||||
|
|
||||||
|
if (!results) {
|
||||||
|
return <antd.Empty />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof results !== "object") {
|
||||||
|
return <antd.Empty />
|
||||||
|
}
|
||||||
|
|
||||||
|
let keys = Object.keys(results)
|
||||||
|
|
||||||
|
if (keys.length === 0) {
|
||||||
|
return <antd.Empty />
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if all keys are valid, if not replace as "others"
|
||||||
|
keys = keys.map((type) => {
|
||||||
|
if (ResultRenders[type]) {
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
return "others"
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleOnClick = (type, value) => {
|
||||||
|
if (typeof props.onClick !== "function") {
|
||||||
|
console.warn("Searcher: onClick is not a function")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.onClick(type, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys.map((type) => {
|
||||||
|
const decorator = ResultsTypeDecorators[type] ?? {
|
||||||
|
label: keys,
|
||||||
|
icon: <Icons.Search />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="category" id={type}>
|
||||||
|
<h3>{createIconRender(decorator.icon)}{decorator.label}</h3>
|
||||||
|
<div className="suggestions">
|
||||||
|
{
|
||||||
|
results[type].map((item) => {
|
||||||
|
return React.createElement(ResultRenders[type], { item, onClick: (...props) => handleOnClick(type, ...props) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (props) => {
|
||||||
|
const [loading, setLoading] = React.useState(false)
|
||||||
|
const [searchResult, setSearchResult] = React.useState(null)
|
||||||
|
const [searchValue, setSearchValue] = React.useState("")
|
||||||
|
|
||||||
|
const makeSearch = async (value) => {
|
||||||
|
if (value === "") {
|
||||||
|
return setSearchResult(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await app.searchEngine.search(value)
|
||||||
|
|
||||||
|
return setSearchResult(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnSearch = (e) => {
|
||||||
|
// not allow to input space as first character
|
||||||
|
if (e.target.value[0] === " ") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setSearchValue(e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResultClick = (type, value) => {
|
||||||
|
switch (type) {
|
||||||
|
case "users": {
|
||||||
|
app.goToAccount(value.username)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "posts": {
|
||||||
|
app.goToPost(value)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
console.warn("Searcher: cannot handle clicks on result of type :", type)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof props.close === "function") {
|
||||||
|
props.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const timer = setTimeout(async () => {
|
||||||
|
setLoading(true)
|
||||||
|
await makeSearch(searchValue)
|
||||||
|
setLoading(false)
|
||||||
|
}, 400)
|
||||||
|
|
||||||
|
return () => clearTimeout(timer)
|
||||||
|
}, [searchValue])
|
||||||
|
|
||||||
|
return <div
|
||||||
|
className={classnames("searcher", { ["open"]: searchValue })}
|
||||||
|
>
|
||||||
|
<antd.Input
|
||||||
|
placeholder="Start typing to search..."
|
||||||
|
onChange={handleOnSearch}
|
||||||
|
value={searchValue}
|
||||||
|
prefix={<Icons.Search />}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
{searchResult && <div className="results">
|
||||||
|
{loading && <antd.Skeleton active />}
|
||||||
|
{
|
||||||
|
!loading && <Results
|
||||||
|
results={searchResult}
|
||||||
|
onClick={handleResultClick} />
|
||||||
|
}
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
}
|
90
packages/app/src/components/Searcher/index.less
Normal file
90
packages/app/src/components/Searcher/index.less
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
.searcher {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
|
||||||
|
.ant-input-affix-wrapper {
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
padding: 0 10px;
|
||||||
|
|
||||||
|
.ant-input-prefix {
|
||||||
|
font-size: 2rem;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input {
|
||||||
|
height: 100%;
|
||||||
|
color: var(--text-color);
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.results {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
background-color: var(--background-color-primary);
|
||||||
|
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.category {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
width: 90%;
|
||||||
|
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
|
.suggestion {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
background-color: var(--background-color-accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-empty {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user