mirror of
https://github.com/usememos/memos.git
synced 2025-02-22 14:17:48 +01:00
chore: update dropdown component
This commit is contained in:
parent
7a6eb53e0f
commit
004713d4cd
@ -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;
|
|
@ -154,13 +154,31 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
|||||||
<span className="field-text name-text">{resource.filename}</span>
|
<span className="field-text name-text">{resource.filename}</span>
|
||||||
<span className="field-text">{resource.type}</span>
|
<span className="field-text">{resource.type}</span>
|
||||||
<div className="buttons-container">
|
<div className="buttons-container">
|
||||||
<Dropdown className="actions-dropdown">
|
<Dropdown
|
||||||
<button onClick={() => handlPreviewBtnClick(resource)}>{t("resources.preview")}</button>
|
actionsClassName="!w-32"
|
||||||
<button onClick={() => handleCopyResourceLinkBtnClick(resource)}>{t("resources.copy-link")}</button>
|
actions={
|
||||||
<button className="delete-btn" onClick={() => handleDeleteResourceBtnClick(resource)}>
|
<>
|
||||||
{t("common.delete")}
|
<button
|
||||||
</button>
|
className="w-full px-3 text-left leading-10 cursor-pointer rounded hover:bg-gray-100"
|
||||||
</Dropdown>
|
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>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
@ -139,18 +139,36 @@ const PreferencesSection = () => {
|
|||||||
{currentUser?.id === user.id ? (
|
{currentUser?.id === user.id ? (
|
||||||
<span className="tip-text">{t("common.yourself")}</span>
|
<span className="tip-text">{t("common.yourself")}</span>
|
||||||
) : (
|
) : (
|
||||||
<Dropdown className="actions-dropdown">
|
<Dropdown
|
||||||
{user.rowStatus === "NORMAL" ? (
|
actionsClassName="!w-24"
|
||||||
<button onClick={() => handleArchiveUserClick(user)}>{t("common.archive")}</button>
|
actions={
|
||||||
) : (
|
|
||||||
<>
|
<>
|
||||||
<button onClick={() => handleRestoreUserClick(user)}>{t("common.restore")}</button>
|
{user.rowStatus === "NORMAL" ? (
|
||||||
<button className="delete" onClick={() => handleDeleteUserClick(user)}>
|
<button
|
||||||
{t("common.delete")}
|
className="w-full px-3 text-left leading-10 cursor-pointer rounded hover:bg-gray-100"
|
||||||
</button>
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,18 +1,23 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import userService from "../services/userService";
|
import userService from "../services/userService";
|
||||||
import { locationService } from "../services";
|
import { locationService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import Icon from "./Icon";
|
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";
|
import "../less/user-banner.less";
|
||||||
|
|
||||||
const UserBanner = () => {
|
const UserBanner = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
const { user, owner } = useAppSelector((state) => state.user);
|
const { user, owner } = useAppSelector((state) => state.user);
|
||||||
const { memos, tags } = useAppSelector((state) => state.memo);
|
const { memos, tags } = useAppSelector((state) => state.memo);
|
||||||
const [shouldShowPopupBtns, setShouldShowPopupBtns] = useState(false);
|
|
||||||
const [username, setUsername] = useState("Memos");
|
const [username, setUsername] = useState("Memos");
|
||||||
const [createdDays, setCreatedDays] = useState(0);
|
const [createdDays, setCreatedDays] = useState(0);
|
||||||
const isVisitorMode = userService.isVisitorMode();
|
const isVisitorMode = userService.isVisitorMode();
|
||||||
@ -34,8 +39,27 @@ const UserBanner = () => {
|
|||||||
locationService.clearQuery();
|
locationService.clearQuery();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handlePopupBtnClick = () => {
|
const handleResourcesBtnClick = () => {
|
||||||
setShouldShowPopupBtns(true);
|
showResourcesDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleArchivedBtnClick = () => {
|
||||||
|
showArchivedMemoDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAboutBtnClick = () => {
|
||||||
|
showAboutSiteDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSignOutBtnClick = async () => {
|
||||||
|
userService
|
||||||
|
.doSignOut()
|
||||||
|
.then(() => {
|
||||||
|
navigate("/auth");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// do nth
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -45,10 +69,39 @@ const UserBanner = () => {
|
|||||||
<span className="username-text">{username}</span>
|
<span className="username-text">{username}</span>
|
||||||
{!isVisitorMode && user?.role === "HOST" ? <span className="tag">MOD</span> : null}
|
{!isVisitorMode && user?.role === "HOST" ? <span className="tag">MOD</span> : null}
|
||||||
</div>
|
</div>
|
||||||
<button className="action-btn menu-popup-btn" onClick={handlePopupBtnClick}>
|
<Dropdown
|
||||||
<Icon.MoreHorizontal className="icon-img" />
|
trigger={<Icon.MoreHorizontal className="w-5 h-auto cursor-pointer" />}
|
||||||
</button>
|
actionsClassName="!w-36"
|
||||||
<MenuBtnsPopup shownStatus={shouldShowPopupBtns} setShownStatus={setShouldShowPopupBtns} />
|
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>
|
||||||
<div className="amount-text-container">
|
<div className="amount-text-container">
|
||||||
<div className="status-text memos-text">
|
<div className="status-text memos-text">
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { ReactNode, useEffect, useRef } from "react";
|
import { ReactNode, useEffect, useRef } from "react";
|
||||||
import useToggle from "../../hooks/useToggle";
|
import useToggle from "../../hooks/useToggle";
|
||||||
import Icon from "../Icon";
|
import Icon from "../Icon";
|
||||||
import "../../less/common/dropdown.less";
|
|
||||||
|
|
||||||
interface DropdownProps {
|
interface Props {
|
||||||
children?: ReactNode;
|
trigger?: ReactNode;
|
||||||
|
actions?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
actionsClassName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dropdown: React.FC<DropdownProps> = (props: DropdownProps) => {
|
const Dropdown: React.FC<Props> = (props: Props) => {
|
||||||
const { children, className } = props;
|
const { trigger, actions, className, actionsClassName } = props;
|
||||||
const [dropdownStatus, toggleDropdownStatus] = useToggle(false);
|
const [dropdownStatus, toggleDropdownStatus] = useToggle(false);
|
||||||
const dropdownWrapperRef = useRef<HTMLDivElement>(null);
|
const dropdownWrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@ -28,11 +29,25 @@ const Dropdown: React.FC<DropdownProps> = (props: DropdownProps) => {
|
|||||||
}, [dropdownStatus]);
|
}, [dropdownStatus]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={dropdownWrapperRef} className={`dropdown-wrapper ${className ?? ""}`} onClick={() => toggleDropdownStatus()}>
|
<div
|
||||||
<span className="trigger-button">
|
ref={dropdownWrapperRef}
|
||||||
<Icon.MoreHorizontal className="icon-img" />
|
className={`relative flex flex-col justify-start items-start select-none ${className ?? ""}`}
|
||||||
</span>
|
onClick={() => toggleDropdownStatus()}
|
||||||
<div className={`action-buttons-container ${dropdownStatus ? "" : "!hidden"}`}>{children}</div>
|
>
|
||||||
|
{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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -45,12 +45,6 @@
|
|||||||
|
|
||||||
> .buttons-container {
|
> .buttons-container {
|
||||||
@apply w-full flex flex-row justify-end items-center;
|
@apply w-full flex flex-row justify-end items-center;
|
||||||
|
|
||||||
> .actions-dropdown {
|
|
||||||
.delete-btn {
|
|
||||||
@apply text-red-600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,12 +52,6 @@
|
|||||||
> .tip-text {
|
> .tip-text {
|
||||||
@apply text-gray-400;
|
@apply text-gray-400;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .actions-dropdown {
|
|
||||||
.delete {
|
|
||||||
@apply text-red-600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user