chore: update dropdown component

This commit is contained in:
Steven 2022-09-20 21:11:33 +08:00
parent 7a6eb53e0f
commit 004713d4cd
9 changed files with 139 additions and 163 deletions

View file

@ -1,82 +0,0 @@
import { useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { userService } from "../services";
import Only from "./common/OnlyWhen";
import showAboutSiteDialog from "./AboutSiteDialog";
import showArchivedMemoDialog from "./ArchivedMemoDialog";
import showResourcesDialog from "./ResourcesDialog";
import "../less/menu-btns-popup.less";
interface Props {
shownStatus: boolean;
setShownStatus: (status: boolean) => void;
}
const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
const { shownStatus, setShownStatus } = props;
const { t } = useTranslation();
const navigate = useNavigate();
const popupElRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (shownStatus) {
const handleClickOutside = (event: MouseEvent) => {
if (!popupElRef.current?.contains(event.target as Node)) {
event.stopPropagation();
}
setShownStatus(false);
};
window.addEventListener("click", handleClickOutside, {
capture: true,
once: true,
});
}
}, [shownStatus]);
const handleResourcesBtnClick = () => {
showResourcesDialog();
};
const handleArchivedBtnClick = () => {
showArchivedMemoDialog();
};
const handleAboutBtnClick = () => {
showAboutSiteDialog();
};
const handleSignOutBtnClick = async () => {
userService
.doSignOut()
.then(() => {
navigate("/auth");
})
.catch(() => {
// do nth
});
};
return (
<div className={`menu-btns-popup ${shownStatus ? "" : "hidden"}`} ref={popupElRef}>
<Only when={!userService.isVisitorMode()}>
<button className="btn action-btn" onClick={handleResourcesBtnClick}>
<span className="icon">🌄</span> {t("sidebar.resources")}
</button>
<button className="btn action-btn" onClick={handleArchivedBtnClick}>
<span className="icon">🗂</span> {t("sidebar.archived")}
</button>
</Only>
<button className="btn action-btn" onClick={handleAboutBtnClick}>
<span className="icon">🤠</span> {t("common.about")}
</button>
<Only when={!userService.isVisitorMode()}>
<button className="btn action-btn" onClick={handleSignOutBtnClick}>
<span className="icon">👋</span> {t("common.sign-out")}
</button>
</Only>
</div>
);
};
export default MenuBtnsPopup;

View file

@ -154,13 +154,31 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
<span className="field-text name-text">{resource.filename}</span>
<span className="field-text">{resource.type}</span>
<div className="buttons-container">
<Dropdown className="actions-dropdown">
<button onClick={() => handlPreviewBtnClick(resource)}>{t("resources.preview")}</button>
<button onClick={() => handleCopyResourceLinkBtnClick(resource)}>{t("resources.copy-link")}</button>
<button className="delete-btn" onClick={() => handleDeleteResourceBtnClick(resource)}>
{t("common.delete")}
</button>
</Dropdown>
<Dropdown
actionsClassName="!w-32"
actions={
<>
<button
className="w-full px-3 text-left leading-10 cursor-pointer rounded hover:bg-gray-100"
onClick={() => handlPreviewBtnClick(resource)}
>
{t("resources.preview")}
</button>
<button
className="w-full px-3 text-left leading-10 cursor-pointer rounded hover:bg-gray-100"
onClick={() => handleCopyResourceLinkBtnClick(resource)}
>
{t("resources.copy-link")}
</button>
<button
className="w-full px-3 text-left leading-10 cursor-pointer rounded text-red-600 hover:bg-gray-100"
onClick={() => handleDeleteResourceBtnClick(resource)}
>
{t("common.delete")}
</button>
</>
}
/>
</div>
</div>
))

View file

@ -139,18 +139,36 @@ const PreferencesSection = () => {
{currentUser?.id === user.id ? (
<span className="tip-text">{t("common.yourself")}</span>
) : (
<Dropdown className="actions-dropdown">
{user.rowStatus === "NORMAL" ? (
<button onClick={() => handleArchiveUserClick(user)}>{t("common.archive")}</button>
) : (
<Dropdown
actionsClassName="!w-24"
actions={
<>
<button onClick={() => handleRestoreUserClick(user)}>{t("common.restore")}</button>
<button className="delete" onClick={() => handleDeleteUserClick(user)}>
{t("common.delete")}
</button>
{user.rowStatus === "NORMAL" ? (
<button
className="w-full px-3 text-left leading-10 cursor-pointer rounded hover:bg-gray-100"
onClick={() => handleArchiveUserClick(user)}
>
{t("common.archive")}
</button>
) : (
<>
<button
className="w-full px-3 text-left leading-10 cursor-pointer rounded hover:bg-gray-100"
onClick={() => handleRestoreUserClick(user)}
>
{t("common.restore")}
</button>
<button
className="w-full px-3 text-left leading-10 cursor-pointer rounded text-red-600 hover:bg-gray-100"
onClick={() => handleDeleteUserClick(user)}
>
{t("common.delete")}
</button>
</>
)}
</>
)}
</Dropdown>
}
/>
)}
</div>
</div>

View file

@ -1,18 +1,23 @@
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import * as utils from "../helpers/utils";
import userService from "../services/userService";
import { locationService } from "../services";
import { useAppSelector } from "../store";
import Icon from "./Icon";
import MenuBtnsPopup from "./MenuBtnsPopup";
import Dropdown from "./common/Dropdown";
import Only from "./common/OnlyWhen";
import showResourcesDialog from "./ResourcesDialog";
import showArchivedMemoDialog from "./ArchivedMemoDialog";
import showAboutSiteDialog from "./AboutSiteDialog";
import "../less/user-banner.less";
const UserBanner = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const { user, owner } = useAppSelector((state) => state.user);
const { memos, tags } = useAppSelector((state) => state.memo);
const [shouldShowPopupBtns, setShouldShowPopupBtns] = useState(false);
const [username, setUsername] = useState("Memos");
const [createdDays, setCreatedDays] = useState(0);
const isVisitorMode = userService.isVisitorMode();
@ -34,8 +39,27 @@ const UserBanner = () => {
locationService.clearQuery();
}, []);
const handlePopupBtnClick = () => {
setShouldShowPopupBtns(true);
const handleResourcesBtnClick = () => {
showResourcesDialog();
};
const handleArchivedBtnClick = () => {
showArchivedMemoDialog();
};
const handleAboutBtnClick = () => {
showAboutSiteDialog();
};
const handleSignOutBtnClick = async () => {
userService
.doSignOut()
.then(() => {
navigate("/auth");
})
.catch(() => {
// do nth
});
};
return (
@ -45,10 +69,39 @@ const UserBanner = () => {
<span className="username-text">{username}</span>
{!isVisitorMode && user?.role === "HOST" ? <span className="tag">MOD</span> : null}
</div>
<button className="action-btn menu-popup-btn" onClick={handlePopupBtnClick}>
<Icon.MoreHorizontal className="icon-img" />
</button>
<MenuBtnsPopup shownStatus={shouldShowPopupBtns} setShownStatus={setShouldShowPopupBtns} />
<Dropdown
trigger={<Icon.MoreHorizontal className="w-5 h-auto cursor-pointer" />}
actionsClassName="!w-36"
actions={
<>
<Only when={!userService.isVisitorMode()}>
<button
className="w-full px-3 text-left leading-10 cursor-pointer rounded hover:bg-gray-100"
onClick={handleResourcesBtnClick}
>
<span className="mr-1">🌄</span> {t("sidebar.resources")}
</button>
<button
className="w-full px-3 text-left leading-10 cursor-pointer rounded hover:bg-gray-100"
onClick={handleArchivedBtnClick}
>
<span className="mr-1">🗂</span> {t("sidebar.archived")}
</button>
</Only>
<button className="w-full px-3 text-left leading-10 cursor-pointer rounded hover:bg-gray-100" onClick={handleAboutBtnClick}>
<span className="mr-1">🤠</span> {t("common.about")}
</button>
<Only when={!userService.isVisitorMode()}>
<button
className="w-full px-3 text-left leading-10 cursor-pointer rounded hover:bg-gray-100"
onClick={handleSignOutBtnClick}
>
<span className="mr-1">👋</span> {t("common.sign-out")}
</button>
</Only>
</>
}
/>
</div>
<div className="amount-text-container">
<div className="status-text memos-text">

View file

@ -1,15 +1,16 @@
import { ReactNode, useEffect, useRef } from "react";
import useToggle from "../../hooks/useToggle";
import Icon from "../Icon";
import "../../less/common/dropdown.less";
interface DropdownProps {
children?: ReactNode;
interface Props {
trigger?: ReactNode;
actions?: ReactNode;
className?: string;
actionsClassName?: string;
}
const Dropdown: React.FC<DropdownProps> = (props: DropdownProps) => {
const { children, className } = props;
const Dropdown: React.FC<Props> = (props: Props) => {
const { trigger, actions, className, actionsClassName } = props;
const [dropdownStatus, toggleDropdownStatus] = useToggle(false);
const dropdownWrapperRef = useRef<HTMLDivElement>(null);
@ -28,11 +29,25 @@ const Dropdown: React.FC<DropdownProps> = (props: DropdownProps) => {
}, [dropdownStatus]);
return (
<div ref={dropdownWrapperRef} className={`dropdown-wrapper ${className ?? ""}`} onClick={() => toggleDropdownStatus()}>
<span className="trigger-button">
<Icon.MoreHorizontal className="icon-img" />
</span>
<div className={`action-buttons-container ${dropdownStatus ? "" : "!hidden"}`}>{children}</div>
<div
ref={dropdownWrapperRef}
className={`relative flex flex-col justify-start items-start select-none ${className ?? ""}`}
onClick={() => toggleDropdownStatus()}
>
{trigger ? (
trigger
) : (
<button className="flex flex-row justify-center items-center border p-1 rounded shadow text-gray-600 cursor-pointer hover:opacity-80">
<Icon.MoreHorizontal className="w-4 h-auto" />
</button>
)}
<div
className={`w-auto mt-1 absolute top-full right-0 flex flex-col justify-start items-start bg-white z-1 border p-1 rounded-md shadow ${
actionsClassName ?? ""
} ${dropdownStatus ? "" : "!hidden"}`}
>
{actions}
</div>
</div>
);
};

View file

@ -1,21 +0,0 @@
@import "../mixin.less";
.dropdown-wrapper {
@apply relative flex flex-col justify-start items-start select-none;
> .trigger-button {
@apply flex flex-row justify-center items-center border p-1 rounded shadow text-gray-600 cursor-pointer hover:opacity-80;
> .icon-img {
@apply w-4 h-auto;
}
}
> .action-buttons-container {
@apply w-28 mt-1 absolute top-full right-0 flex flex-col justify-start items-start bg-white z-1 border p-1 rounded shadow;
> button {
@apply w-full text-left px-2 text-sm leading-7 rounded hover:bg-gray-100;
}
}
}

View file

@ -1,13 +0,0 @@
@import "./mixin.less";
.menu-btns-popup {
@apply absolute right-2 top-6 flex flex-col justify-start items-start mt-4 p-1 w-36 rounded-lg z-10 shadow bg-white;
> .btn {
@apply flex flex-row justify-start items-center w-full py-2 px-3 text-base rounded text-left hover:bg-gray-100;
> .icon {
@apply block w-6 text-center mr-2 text-base;
}
}
}

View file

@ -45,12 +45,6 @@
> .buttons-container {
@apply w-full flex flex-row justify-end items-center;
> .actions-dropdown {
.delete-btn {
@apply text-red-600;
}
}
}
}

View file

@ -52,12 +52,6 @@
> .tip-text {
@apply text-gray-400;
}
> .actions-dropdown {
.delete {
@apply text-red-600;
}
}
}
}
}