From f5dfd52f6efece2f4cad227044ecf4dd66301bbc Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 26 Aug 2023 21:36:58 +0800 Subject: ... --- FrontEnd/src/components/menu/Menu.css | 36 +++++++++++++++ FrontEnd/src/components/menu/Menu.tsx | 62 +++++++++++++++++++++++++ FrontEnd/src/components/menu/PopupMenu.css | 7 +++ FrontEnd/src/components/menu/PopupMenu.tsx | 73 ++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 FrontEnd/src/components/menu/Menu.css create mode 100644 FrontEnd/src/components/menu/Menu.tsx create mode 100644 FrontEnd/src/components/menu/PopupMenu.css create mode 100644 FrontEnd/src/components/menu/PopupMenu.tsx (limited to 'FrontEnd/src/components/menu') 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..e8099c76 --- /dev/null +++ b/FrontEnd/src/components/menu/Menu.tsx @@ -0,0 +1,62 @@ +import { CSSProperties } from "react"; +import classNames from "classnames"; + +import { useC, Text, ThemeColor } from "../common"; + +import "./Menu.css"; +import Icon from "../Icon"; + +export type MenuItem = + | { + type: "divider"; + } + | { + type: "button"; + text: Text; + icon?: string; + color?: ThemeColor; + onClick: () => void; + }; + +export type MenuItems = MenuItem[]; + +export type MenuProps = { + items: MenuItems; + onItemClicked?: () => void; + className?: string; + style?: CSSProperties; +}; + +export default function Menu({ + items, + onItemClicked, + className, + style, +}: MenuProps) { + const c = useC(); + + return ( +
+ {items.map((item, index) => { + if (item.type === "divider") { + return
; + } else { + const { text, color, icon, onClick } = item; + return ( + + ); + } + })} +
+ ); +} 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..23a67f79 --- /dev/null +++ b/FrontEnd/src/components/menu/PopupMenu.tsx @@ -0,0 +1,73 @@ +import { useState, CSSProperties, ReactNode } from "react"; +import classNames from "classnames"; +import { createPortal } from "react-dom"; +import { usePopper } from "react-popper"; + +import { useClickOutside } from "~src/utilities/hooks"; + +import Menu, { MenuItems } from "./Menu"; + +import { ThemeColor } from "../common"; + +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(false); + + const [referenceElement, setReferenceElement] = + useState(null); + const [popperElement, setPopperElement] = useState( + null, + ); + const { styles, attributes } = usePopper(referenceElement, popperElement); + + useClickOutside(popperElement, () => setShow(false), true); + + return ( +
setShow(true)} + > + {children} + {show && + createPortal( +
+ { + setShow(false); + }} + /> +
, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + document.getElementById("portal")!, + )} +
+ ); +} -- cgit v1.2.3