aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/components/menu
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2023-09-20 20:26:42 +0800
committerGitHub <noreply@github.com>2023-09-20 20:26:42 +0800
commitf836d77e73f3ea0af45c5f71dae7268143d6d86f (patch)
tree573cfafd972106d69bef0d41ff5f270ec3c43ec2 /FrontEnd/src/components/menu
parent4a069bf1268f393d5467166356f691eb89963152 (diff)
parent901fe3d7c032d284da5c9bce24c4aaee9054c7ac (diff)
downloadtimeline-f836d77e73f3ea0af45c5f71dae7268143d6d86f.tar.gz
timeline-f836d77e73f3ea0af45c5f71dae7268143d6d86f.tar.bz2
timeline-f836d77e73f3ea0af45c5f71dae7268143d6d86f.zip
Merge pull request #1395 from crupest/dev
Refector 2023 v0.1
Diffstat (limited to 'FrontEnd/src/components/menu')
-rw-r--r--FrontEnd/src/components/menu/Menu.css36
-rw-r--r--FrontEnd/src/components/menu/Menu.tsx62
-rw-r--r--FrontEnd/src/components/menu/PopupMenu.css7
-rw-r--r--FrontEnd/src/components/menu/PopupMenu.tsx72
4 files changed, 177 insertions, 0 deletions
diff --git a/FrontEnd/src/components/menu/Menu.css b/FrontEnd/src/components/menu/Menu.css
new file mode 100644
index 00000000..75734533
--- /dev/null
+++ b/FrontEnd/src/components/menu/Menu.css
@@ -0,0 +1,36 @@
+.cru-menu {
+ min-width: 200px;
+}
+
+.cru-menu-item {
+ display: block;
+ font-size: 1em;
+ width: 100%;
+ padding: 0.5em 1.5em;
+ transition: all 0.5s;
+ color: var(--cru-clickable-normal-color);
+ background-color: var(--cru-clickable-grayscale-normal-color);
+ border: none;
+ cursor: pointer;
+}
+
+.cru-menu-item:hover {
+ background-color: var(--cru-clickable-grayscale-hover-color);
+}
+
+.cru-menu-item:focus {
+ background-color: var(--cru-clickable-grayscale-focus-color);
+}
+
+.cru-menu-item:active {
+ background-color: var(--cru-clickable-grayscale-active-color);
+}
+
+.cru-menu-item-icon {
+ margin-right: 1em;
+}
+
+.cru-menu-divider {
+ border-width: 0;
+ border-top: 1px solid var(--cru-primary-color);
+} \ No newline at end of file
diff --git a/FrontEnd/src/components/menu/Menu.tsx b/FrontEnd/src/components/menu/Menu.tsx
new file mode 100644
index 00000000..1a196a69
--- /dev/null
+++ b/FrontEnd/src/components/menu/Menu.tsx
@@ -0,0 +1,62 @@
+import { MouseEvent, CSSProperties } from "react";
+import classNames from "classnames";
+
+import { useC, Text, ThemeColor } from "../common";
+import Icon from "../Icon";
+
+import "./Menu.css";
+
+export type MenuItem =
+ | {
+ type: "divider";
+ }
+ | {
+ type: "button";
+ text: Text;
+ icon?: string;
+ color?: ThemeColor;
+ onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
+ };
+
+export type MenuItems = MenuItem[];
+
+export type MenuProps = {
+ items: MenuItems;
+ onItemClick?: (e: MouseEvent<HTMLButtonElement>) => void;
+ className?: string;
+ style?: CSSProperties;
+};
+
+export default function Menu({
+ items,
+ onItemClick,
+ className,
+ style,
+}: MenuProps) {
+ const c = useC();
+
+ return (
+ <div className={classNames("cru-menu", className)} style={style}>
+ {items.map((item, index) => {
+ if (item.type === "divider") {
+ return <hr key={index} className="cru-menu-divider" />;
+ } else {
+ const { text, color, icon, onClick } = item;
+ return (
+ <button
+ key={index}
+ className={`cru-menu-item cru-clickable-${color ?? "primary"}`}
+ onClick={(e) => {
+ onClick?.(e);
+ onItemClick?.(e);
+ }}
+ >
+ {icon != null && <Icon color={color} icon={icon} />}
+ {c(text)}
+ </button>
+ );
+ }
+ })}
+ </div>
+ );
+}
diff --git a/FrontEnd/src/components/menu/PopupMenu.css b/FrontEnd/src/components/menu/PopupMenu.css
new file mode 100644
index 00000000..149e0699
--- /dev/null
+++ b/FrontEnd/src/components/menu/PopupMenu.css
@@ -0,0 +1,7 @@
+.cru-popup-menu-menu-container {
+ z-index: 1040;
+ border-radius: 3px;
+ border: var(--cru-clickable-normal-color) 1.5px solid;
+ background-color: var(--cru-background-color);
+ overflow: hidden;
+}
diff --git a/FrontEnd/src/components/menu/PopupMenu.tsx b/FrontEnd/src/components/menu/PopupMenu.tsx
new file mode 100644
index 00000000..7ac2abfe
--- /dev/null
+++ b/FrontEnd/src/components/menu/PopupMenu.tsx
@@ -0,0 +1,72 @@
+import { useState, CSSProperties, ReactNode } from "react";
+import classNames from "classnames";
+import { createPortal } from "react-dom";
+import { usePopper } from "react-popper";
+
+import { ThemeColor } from "../common";
+import { useClickOutside } from "../hooks";
+import Menu, { MenuItems } from "./Menu";
+
+import "./PopupMenu.css";
+
+export interface PopupMenuProps {
+ color?: ThemeColor;
+ items: MenuItems;
+ children?: ReactNode;
+ containerClassName?: string;
+ containerStyle?: CSSProperties;
+}
+
+export default function PopupMenu({
+ color,
+ items,
+ children,
+ containerClassName,
+ containerStyle,
+}: PopupMenuProps) {
+ const [show, setShow] = useState<boolean>(false);
+
+ const [referenceElement, setReferenceElement] =
+ useState<HTMLDivElement | null>(null);
+ const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(
+ null,
+ );
+ const { styles, attributes } = usePopper(referenceElement, popperElement);
+
+ useClickOutside(popperElement, () => setShow(false), true);
+
+ return (
+ <div
+ ref={setReferenceElement}
+ className={classNames(
+ "cru-popup-menu-trigger-container",
+ containerClassName,
+ )}
+ style={containerStyle}
+ onClick={() => setShow(true)}
+ >
+ {children}
+ {show &&
+ createPortal(
+ <div
+ ref={setPopperElement}
+ className={`cru-popup-menu-menu-container cru-clickable-${
+ color ?? "primary"
+ }`}
+ style={styles.popper}
+ {...attributes.popper}
+ >
+ <Menu
+ items={items}
+ onItemClick={(e) => {
+ setShow(false);
+ e.stopPropagation();
+ }}
+ />
+ </div>,
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ document.getElementById("portal")!,
+ )}
+ </div>
+ );
+}