mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
improve searcher
This commit is contained in:
parent
2f02acbc68
commit
010ae14844
@ -1,90 +1,132 @@
|
||||
import React from "react"
|
||||
import * as antd from "antd"
|
||||
import classnames from "classnames"
|
||||
import useUrlQueryActiveKey from "hooks/useUrlQueryActiveKey"
|
||||
import lodash from "lodash"
|
||||
import useUrlQueryActiveKey from "hooks/useUrlQueryActiveKey"
|
||||
import { Translation } from "react-i18next"
|
||||
|
||||
import { UserPreview } from "components"
|
||||
import { Icons, createIconRender } from "components/Icons"
|
||||
|
||||
import UserPreview from "components/UserPreview"
|
||||
import MusicTrack from "components/MusicTrack"
|
||||
import PlaylistItem from "components/PlaylistItem"
|
||||
|
||||
import SearchModel from "models/search"
|
||||
|
||||
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"
|
||||
label: "Users",
|
||||
renderItem: (props) => {
|
||||
const { item, onClick } = props
|
||||
|
||||
return <div className="suggestion">
|
||||
<UserPreview onClick={onClick} user={item} />
|
||||
</div>
|
||||
}
|
||||
},
|
||||
tracks: {
|
||||
icon: "Album",
|
||||
label: "Tracks",
|
||||
renderItem: (props) => {
|
||||
const { item, onClick } = props
|
||||
|
||||
return <div className="suggestion" onClick={onClick}>
|
||||
<MusicTrack track={item} />
|
||||
</div>
|
||||
}
|
||||
},
|
||||
playlists: {
|
||||
icon: "Album",
|
||||
label: "Playlists",
|
||||
renderItem: (props) => {
|
||||
return <div className="suggestion">
|
||||
<PlaylistItem playlist={props.item} />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Results = (props) => {
|
||||
let { results } = props
|
||||
|
||||
if (!results) {
|
||||
return <antd.Empty />
|
||||
}
|
||||
|
||||
if (typeof results !== "object") {
|
||||
return <antd.Empty />
|
||||
return null
|
||||
}
|
||||
|
||||
let keys = Object.keys(results)
|
||||
let groupsKeys = 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"
|
||||
// filter out empty groups
|
||||
groupsKeys = groupsKeys.filter((key) => {
|
||||
return results[key].length > 0
|
||||
})
|
||||
|
||||
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}>
|
||||
<div className="suggestions">
|
||||
<span>
|
||||
<span className="icon">{createIconRender(decorator.icon)}</span>
|
||||
<span className="label">{decorator.label}</span>
|
||||
</span>
|
||||
{
|
||||
results[type].map((item) => {
|
||||
return React.createElement(ResultRenders[type], { item, onClick: (...props) => handleOnClick(type, ...props) })
|
||||
})
|
||||
}
|
||||
</div>
|
||||
if (groupsKeys.length === 0) {
|
||||
return <div className="searcher no_results">
|
||||
<antd.Result
|
||||
status="info"
|
||||
title="No results"
|
||||
subTitle="We are sorry, but we could not find any results for your search."
|
||||
/>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = (props) => {
|
||||
if (props.close) {
|
||||
props.close()
|
||||
}
|
||||
}
|
||||
|
||||
return <div
|
||||
className={classnames(
|
||||
"searcher_results",
|
||||
{
|
||||
["one_column"]: groupsKeys.length === 1,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{
|
||||
groupsKeys.map((key, index) => {
|
||||
const decorator = ResultsTypeDecorators[key] ?? {
|
||||
icon: null,
|
||||
label: key,
|
||||
renderItem: () => null
|
||||
}
|
||||
|
||||
return <div
|
||||
className="searcher_results_category"
|
||||
key={index}
|
||||
>
|
||||
<div className="searcher_results_category_header">
|
||||
<h1>
|
||||
{
|
||||
createIconRender(decorator.icon)
|
||||
}
|
||||
<Translation>
|
||||
{(t) => t(decorator.label)}
|
||||
</Translation>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="searcher_results_category_suggestions" id={key}>
|
||||
{
|
||||
results[key].map((item, index) => {
|
||||
return decorator.renderItem({
|
||||
key: index,
|
||||
item,
|
||||
onClick: handleClick,
|
||||
...decorator.props,
|
||||
})
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default (props) => {
|
||||
const [loading, setLoading] = React.useState(false)
|
||||
|
@ -9,7 +9,14 @@
|
||||
|
||||
transition: all 0.3s ease-in-out;
|
||||
|
||||
gap: 20px;
|
||||
gap: 10px;
|
||||
|
||||
.results {
|
||||
border-radius: 10px;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: overlay;
|
||||
}
|
||||
|
||||
&.small {
|
||||
.ant-input-affix-wrapper {
|
||||
@ -28,6 +35,11 @@
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper {
|
||||
position: sticky;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
|
||||
@ -37,6 +49,7 @@
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
min-height: 60px;
|
||||
height: 60px;
|
||||
border-radius: 10px;
|
||||
|
||||
@ -59,57 +72,90 @@
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.results {
|
||||
position: relative;
|
||||
.searcher_results {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
width: 50vw;
|
||||
|
||||
gap: 10px;
|
||||
|
||||
&.one_column {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
&.no_results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
width: 100%;
|
||||
|
||||
padding: 10px;
|
||||
|
||||
.searcher_results_category {
|
||||
background-color: var(--background-color-primary);
|
||||
|
||||
padding: 20px;
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
gap: 10px;
|
||||
height: fit-content;
|
||||
width: 100%;
|
||||
|
||||
.category {
|
||||
h3 {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
gap: 20px;
|
||||
|
||||
.searcher_results_category_header {
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
width: 100%;
|
||||
.searcher_results_category_suggestions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 10px;
|
||||
|
||||
.suggestions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 10px;
|
||||
|
||||
gap: 10px;
|
||||
.playlistItem {
|
||||
background-color: var(--background-color-accent);
|
||||
|
||||
.suggestion {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
height: 80px;
|
||||
|
||||
padding: 10px;
|
||||
.playlistItem_cover {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
|
||||
border-radius: 8px;
|
||||
img {
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
background-color: var(--background-color-accent);
|
||||
.playlistItem_info {
|
||||
max-width: unset;
|
||||
|
||||
h1 {
|
||||
font-size: 1rem;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.music-track {
|
||||
background-color: var(--background-color-accent);
|
||||
}
|
||||
}
|
||||
|
||||
#playlists {
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-empty {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user