diff --git a/packages/app/src/components/Searcher/index.jsx b/packages/app/src/components/Searcher/index.jsx new file mode 100644 index 00000000..7e31a484 --- /dev/null +++ b/packages/app/src/components/Searcher/index.jsx @@ -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
+ +
+ } +} + +const ResultsTypeDecorators = { + users: { + icon: "Users", + label: "Users" + } +} + +const Results = (props) => { + let { results } = props + + if (!results) { + return + } + + if (typeof results !== "object") { + return + } + + let keys = Object.keys(results) + + if (keys.length === 0) { + return + } + + // 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: + } + + return
+

{createIconRender(decorator.icon)}{decorator.label}

+
+ { + results[type].map((item) => { + return React.createElement(ResultRenders[type], { item, onClick: (...props) => handleOnClick(type, ...props) }) + }) + } +
+
+ }) +} + +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
+ } + autoFocus + /> + + {searchResult &&
+ {loading && } + { + !loading && + } +
} +
+} \ No newline at end of file diff --git a/packages/app/src/components/Searcher/index.less b/packages/app/src/components/Searcher/index.less new file mode 100644 index 00000000..139f2f49 --- /dev/null +++ b/packages/app/src/components/Searcher/index.less @@ -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; + } +} \ No newline at end of file