diff --git a/packages/app/src/components/UserSelector/index.jsx b/packages/app/src/components/UserSelector/index.jsx new file mode 100644 index 00000000..1de361ed --- /dev/null +++ b/packages/app/src/components/UserSelector/index.jsx @@ -0,0 +1,231 @@ +import React from "react" +import * as antd from "antd" +import classnames from "classnames" + +import { UserPreview } from "components" +import { Icons, createIconRender } from "components/Icons" + +import useRequest from "comty.js/hooks/useRequest" + +import SearchModel from "comty.js/models/search" + +import "./index.less" + +const ResultsTypeDecorators = { + "friends": { + icon: "MdPeople", + label: "Recent" + }, + "users": { + icon: "Users", + label: "Users" + } +} + +const SelectableResults = (props) => { + const [invitedUsers, setInvitedUsers] = React.useState(props.invitedUsers ?? []) + + let { results } = props + + if (!results) { + return + } + + if (typeof results !== "object") { + return + } + + let keys = React.useMemo(() => { + let _keys = Object.keys(results) + + // check if all keys are valid, if not replace as "others" + _keys = _keys.map((type) => { + if (ResultsTypeDecorators[type]) { + return type + } + + return "others" + }) + + return _keys + }, [results]) + + if (keys.length === 0) { + return + } + + const handleOnClick = (value) => { + if (props.onInviteUser) { + props.onInviteUser(value) + } + } + + React.useEffect(() => { + if (!props.invitedUsers || !Array.isArray(props.invitedUsers)) { + return + } + + setInvitedUsers(props.invitedUsers) + }, [props.invitedUsers]) + + return <> + { + keys.map((type, index) => { + const result = results[type] + + if (!result || result.length === 0) { + return null + } + + return
+
+ { + createIconRender(ResultsTypeDecorators[type].icon) + } + {ResultsTypeDecorators[type].label ?? "Unknown"} +
+ +
+ { + result.map((item, index) => { + const invited = invitedUsers.find((user) => user._id === item._id) + + return
+ { }} /> + + handleOnClick(item)} + disabled={invited} + > + { + invited ? "Invited" : "Invite" + } + +
+ }) + } +
+
+ }) + } + +} + +const UserSelector = (props) => { + const [loading, setLoading] = React.useState(false) + + const [invitedUsers, setInvitedUsers] = React.useState([]) + + const [searchResult, setSearchResult] = React.useState(null) + const [searchValue, setSearchValue] = React.useState("") + + const [L_QuickSearch, R_QuickSearch, E_QuickSearch, M_QuickSearch] = useRequest(SearchModel.quickSearch,) + + const makeSearch = async (value) => { + if (value === "") { + return setSearchResult(null) + } + + // make search request + const result = await SearchModel.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 handleUserInvite = (user) => { + setInvitedUsers((users) => { + return [...users, user] + }) + + app.cores.sync.music.inviteToUser(user._id) + + // create a timeout to remove user from invited list in 10 seconds + } + + React.useEffect(() => { + const timer = setTimeout(async () => { + setLoading(true) + + await makeSearch(searchValue) + + setLoading(false) + }, 400) + + return () => clearTimeout(timer) + }, [searchValue]) + + return
+ } + autoFocus={props.autoFocus ?? false} + onFocus={props.onFocus} + onBlur={props.onUnfocus} + /> + + { + searchResult &&
+ { + loading && + } + { + !loading && + } +
+ } + + { + !searchResult &&
+ { + L_QuickSearch && + } + { + E_QuickSearch && + } + { + !L_QuickSearch && !E_QuickSearch && + } +
+ } +
+} + +export const openModal = (props) => { + return app.ModalController.open(() => ) +} \ No newline at end of file diff --git a/packages/app/src/components/UserSelector/index.less b/packages/app/src/components/UserSelector/index.less new file mode 100644 index 00000000..bdb36465 --- /dev/null +++ b/packages/app/src/components/UserSelector/index.less @@ -0,0 +1,79 @@ +.user-selector { + display: flex; + flex-direction: column; + + gap: 20px; + + .user-selector_actions { + display: flex; + flex-direction: row; + + align-items: center; + + width: 100%; + + gap: 10px; + } + + .user-selector_results { + display: flex; + flex-direction: column; + + gap: 20px; + + .user-selector_group { + gap: 10px; + + background-color: var(--background-color-primary); + border-radius: 12px; + + padding: 10px; + + .user-selector_group_header { + font-size: 0.9rem; + } + + .user-selector_group_results { + display: flex; + flex-direction: column; + + gap: 5px; + + .user-selector_result_item { + display: flex; + flex-direction: row; + + justify-content: space-between; + align-items: center; + + padding: 6px; + + border-radius: 12px; + + transition: all 0.2s ease-in-out; + + &.selected { + background-color: var(--background-color-accent); + } + + &.hidden { + opacity: 0; + height: 0px; + } + + .userPreview { + width: 100%; + + .info { + width: 90%; + } + + .avatar { + transform: none; + } + } + } + } + } + } +} \ No newline at end of file