From de7df0e4fb38863b830a104a5a38d0e5247679f8 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 29 Jun 2021 23:50:55 +0800 Subject: ... --- FrontEnd/src/views/common/menu/Menu.tsx | 66 +++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 FrontEnd/src/views/common/menu/Menu.tsx (limited to 'FrontEnd/src/views/common/menu/Menu.tsx') diff --git a/FrontEnd/src/views/common/menu/Menu.tsx b/FrontEnd/src/views/common/menu/Menu.tsx new file mode 100644 index 00000000..7523247d --- /dev/null +++ b/FrontEnd/src/views/common/menu/Menu.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import classnames from "classnames"; +import { useTranslation } from "react-i18next"; + +import { convertI18nText, I18nText } from "@/common"; +import { PaletteColorType } from "@/palette"; + +export type MenuItem = + | { + type: "divider"; + } + | { + type: "button"; + text: I18nText; + iconClassName?: string; + color?: PaletteColorType; + onClick: () => void; + }; + +export type MenuItems = MenuItem[]; + +export interface MenuProps { + items: MenuItems; + className?: string; + onItemClicked?: () => void; +} + +const Menu: React.FC = ({ items, className, onItemClicked }) => { + const { t } = useTranslation(); + + return ( +
+ {items.map((item, index) => { + if (item.type === "divider") { + return
; + } else { + return ( +
{ + item.onClick(); + onItemClicked?.(); + }} + > + {item.iconClassName != null ? ( + + ) : null} + {convertI18nText(item.text, t)} +
+ ); + } + })} +
+ ); +}; + +export default Menu; -- cgit v1.2.3 From 6b5d88a6a1a3fe12721f3159a6837d3f41d69022 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 30 Jun 2021 00:15:20 +0800 Subject: ... --- FrontEnd/package.json | 2 + FrontEnd/src/views/common/menu/Menu.tsx | 22 +++++---- FrontEnd/src/views/common/menu/PopupMenu.css | 3 ++ FrontEnd/src/views/common/menu/PopupMenu.tsx | 56 ++++++++++++++++++++-- .../timeline-common/TimelinePageCardTemplate.tsx | 3 +- .../src/views/timeline-common/TimelinePostEdit.tsx | 2 +- 6 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 FrontEnd/src/views/common/menu/PopupMenu.css (limited to 'FrontEnd/src/views/common/menu/Menu.tsx') diff --git a/FrontEnd/package.json b/FrontEnd/package.json index 55c7c95f..2c92d6b2 100644 --- a/FrontEnd/package.json +++ b/FrontEnd/package.json @@ -7,6 +7,7 @@ "description": "Timeline app.", "dependencies": { "@microsoft/signalr": "^5.0.7", + "@popperjs/core": "^2.9.2", "axios": "^0.21.1", "bootstrap": "^5.0.2", "bootstrap-icons": "^1.5.0", @@ -23,6 +24,7 @@ "react-color": "^2.19.3", "react-dom": "^17.0.2", "react-i18next": "^11.11.0", + "react-popper": "^2.2.5", "react-responsive": "^8.2.0", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", diff --git a/FrontEnd/src/views/common/menu/Menu.tsx b/FrontEnd/src/views/common/menu/Menu.tsx index 7523247d..04243fa3 100644 --- a/FrontEnd/src/views/common/menu/Menu.tsx +++ b/FrontEnd/src/views/common/menu/Menu.tsx @@ -5,6 +5,8 @@ import { useTranslation } from "react-i18next"; import { convertI18nText, I18nText } from "@/common"; import { PaletteColorType } from "@/palette"; +import "./Menu.css"; + export type MenuItem = | { type: "divider"; @@ -19,17 +21,23 @@ export type MenuItem = export type MenuItems = MenuItem[]; -export interface MenuProps { +export type MenuProps = { items: MenuItems; - className?: string; onItemClicked?: () => void; -} + className?: string; + style?: React.CSSProperties; +}; -const Menu: React.FC = ({ items, className, onItemClicked }) => { +export default function _Menu({ + items, + onItemClicked, + className, + style, +}: MenuProps): React.ReactElement | null { const { t } = useTranslation(); return ( -
+
{items.map((item, index) => { if (item.type === "divider") { return
; @@ -61,6 +69,4 @@ const Menu: React.FC = ({ items, className, onItemClicked }) => { })}
); -}; - -export default Menu; +} diff --git a/FrontEnd/src/views/common/menu/PopupMenu.css b/FrontEnd/src/views/common/menu/PopupMenu.css new file mode 100644 index 00000000..8465a1bb --- /dev/null +++ b/FrontEnd/src/views/common/menu/PopupMenu.css @@ -0,0 +1,3 @@ +.cru-popup-menu-menu-container { + +} diff --git a/FrontEnd/src/views/common/menu/PopupMenu.tsx b/FrontEnd/src/views/common/menu/PopupMenu.tsx index 0d447f09..50f80a91 100644 --- a/FrontEnd/src/views/common/menu/PopupMenu.tsx +++ b/FrontEnd/src/views/common/menu/PopupMenu.tsx @@ -1,17 +1,63 @@ +import classNames from "classnames"; import React from "react"; +import { usePopper } from "react-popper"; import Menu, { MenuItems } from "./Menu"; +import "./PopupMenu.css"; + export interface PopupMenuProps { items: MenuItems; - children: React.ReactElement; + children?: React.ReactNode; + containerClassName?: string; + containerStyle?: React.CSSProperties; } -export const PopupMenu: React.FC = ({ items, children }) => { +const PopupMenu: React.FC = ({ + items, + children, + containerClassName, + containerStyle, +}) => { const [show, setShow] = React.useState(false); - const toggle = (): void => setShow(!show); - // TODO: + const [referenceElement, setReferenceElement] = + React.useState(null); + const [popperElement, setPopperElement] = + React.useState(null); + const [arrowElement, setArrowElement] = React.useState( + null + ); + const { styles, attributes } = usePopper(referenceElement, popperElement, { + modifiers: [{ name: "arrow", options: { element: arrowElement } }], + }); - return setShow(false)} />; + return ( + <> +
setShow(true)} + > + {children} +
+ {show ? ( +
+ +
+
+ ) : null} + + ); }; + +export default PopupMenu; diff --git a/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx b/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx index 78af1131..75f2d400 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx @@ -17,7 +17,8 @@ import CollapseButton from "./CollapseButton"; import { TimelineMemberDialog } from "./TimelineMember"; import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; import ConnectionStatusBadge from "./ConnectionStatusBadge"; -import { MenuItems, PopupMenu } from "../common/menu/Menu"; +import { MenuItems } from "../common/menu/Menu"; +import PopupMenu from "../common/menu/PopupMenu"; import FullPageDialog from "../common/dailog/FullPageDialog"; import Card from "../common/Card"; diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx index 1e6591c0..e2045429 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx @@ -16,7 +16,7 @@ import { base64 } from "@/http/common"; import BlobImage from "../common/BlobImage"; import LoadingButton from "../common/button/LoadingButton"; -import { PopupMenu } from "../common/menu/Menu"; +import PopupMenu from "../common/menu/PopupMenu"; import Card from "../common/Card"; import MarkdownPostEdit from "./MarkdownPostEdit"; import TimelineLine from "./TimelineLine"; -- cgit v1.2.3 From 47175cf9afcd86b0b92f7121ce0c3d85b948b6dd Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 1 Jul 2021 19:59:22 +0800 Subject: ... --- FrontEnd/src/services/alert.ts | 14 ++-- FrontEnd/src/views/admin/UserAdmin.tsx | 8 +- FrontEnd/src/views/common/alert/AlertHost.tsx | 45 +++++++---- FrontEnd/src/views/common/alert/alert.css | 28 +++++++ FrontEnd/src/views/common/button/Button.css | 60 +++----------- FrontEnd/src/views/common/button/FlatButton.css | 38 +-------- FrontEnd/src/views/common/button/TextButton.css | 36 --------- FrontEnd/src/views/common/button/TextButton.tsx | 18 ----- FrontEnd/src/views/common/button/common.ts | 6 +- FrontEnd/src/views/common/index.css | 100 ++++++++++++++++++++++++ FrontEnd/src/views/common/menu/Menu.css | 47 +---------- FrontEnd/src/views/common/menu/Menu.tsx | 2 +- 12 files changed, 189 insertions(+), 213 deletions(-) delete mode 100644 FrontEnd/src/views/common/button/TextButton.css delete mode 100644 FrontEnd/src/views/common/button/TextButton.tsx (limited to 'FrontEnd/src/views/common/menu/Menu.tsx') diff --git a/FrontEnd/src/services/alert.ts b/FrontEnd/src/services/alert.ts index 48d482ea..abe56315 100644 --- a/FrontEnd/src/services/alert.ts +++ b/FrontEnd/src/services/alert.ts @@ -1,11 +1,13 @@ import React from "react"; import pull from "lodash/pull"; -import { BootstrapThemeColor, I18nText } from "@/common"; +import { I18nText } from "@/common"; +import { PaletteColorType } from "@/palette"; export interface AlertInfo { - type?: BootstrapThemeColor; - message: React.FC | I18nText; + type?: PaletteColorType; + message?: I18nText; + customMessage?: React.ReactElement; dismissTime?: number | "never"; } @@ -55,9 +57,3 @@ export const alertService = new AlertService(); export function pushAlert(alert: AlertInfo): void { alertService.push(alert); } - -export const kAlertHostId = "alert-host"; - -export function getAlertHost(): HTMLElement | null { - return document.getElementById(kAlertHostId); -} diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx index 6d2760f2..4ceff8ab 100644 --- a/FrontEnd/src/views/admin/UserAdmin.tsx +++ b/FrontEnd/src/views/admin/UserAdmin.tsx @@ -14,8 +14,8 @@ import { } from "@/http/user"; import { Trans, useTranslation } from "react-i18next"; import Button from "../common/button/Button"; -import TextButton from "../common/button/TextButton"; import Spinner from "../common/Spinner"; +import FlatButton from "../common/button/FlatButton"; interface DialogProps { open: boolean; @@ -232,12 +232,12 @@ const UserItem: React.FC = ({ user, on }) => { className={classnames("edit-mask", !editMaskVisible && "d-none")} onClick={() => setEditMaskVisible(false)} > - - + - void; @@ -51,14 +49,29 @@ export const AutoCloseAlert: React.FC = (props) => { }; return ( -
- {(() => { - const { message } = alert; - if (typeof message === "function") { - const Message = message; - return ; - } else return convertI18nText(message, t); - })()} +
+
+ {(() => { + const { message, customMessage } = alert; + if (customMessage != null) { + return customMessage; + } else { + return convertI18nText(message, t); + } + })()} +
+
+ +
); }; @@ -66,8 +79,6 @@ export const AutoCloseAlert: React.FC = (props) => { const AlertHost: React.FC = () => { const [alerts, setAlerts] = React.useState([]); - // react guarantee that state setters are stable, so we don't need to add it to dependency list - React.useEffect(() => { const consume = (alert: AlertInfoEx): void => { setAlerts((old) => [...old, alert]); @@ -80,7 +91,7 @@ const AlertHost: React.FC = () => { }, []); return ( -
+
{alerts.map((alert) => { return ( -): React.ReactElement | null { - return ( -