diff options
Diffstat (limited to 'FrontEnd/src/app/views/common')
-rw-r--r-- | FrontEnd/src/app/views/common/ConfirmDialog.tsx | 40 | ||||
-rw-r--r-- | FrontEnd/src/app/views/common/FlatButton.tsx | 36 | ||||
-rw-r--r-- | FrontEnd/src/app/views/common/Menu.tsx | 5 | ||||
-rw-r--r-- | FrontEnd/src/app/views/common/TabPages.tsx | 74 | ||||
-rw-r--r-- | FrontEnd/src/app/views/common/common.sass | 4 |
5 files changed, 157 insertions, 2 deletions
diff --git a/FrontEnd/src/app/views/common/ConfirmDialog.tsx b/FrontEnd/src/app/views/common/ConfirmDialog.tsx new file mode 100644 index 00000000..72940c51 --- /dev/null +++ b/FrontEnd/src/app/views/common/ConfirmDialog.tsx @@ -0,0 +1,40 @@ +import { convertI18nText, I18nText } from "@/common"; +import React from "react"; +import { Modal, Button } from "react-bootstrap"; +import { useTranslation } from "react-i18next"; + +const ConfirmDialog: React.FC<{ + onClose: () => void; + onConfirm: () => void; + title: I18nText; + body: I18nText; +}> = ({ onClose, onConfirm, title, body }) => { + const { t } = useTranslation(); + + return ( + <Modal onHide={onClose} show centered> + <Modal.Header> + <Modal.Title className="text-danger"> + {convertI18nText(title, t)} + </Modal.Title> + </Modal.Header> + <Modal.Body>{convertI18nText(body, t)}</Modal.Body> + <Modal.Footer> + <Button variant="secondary" onClick={onClose}> + {t("operationDialog.cancel")} + </Button> + <Button + variant="danger" + onClick={() => { + onConfirm(); + onClose(); + }} + > + {t("operationDialog.confirm")} + </Button> + </Modal.Footer> + </Modal> + ); +}; + +export default ConfirmDialog; diff --git a/FrontEnd/src/app/views/common/FlatButton.tsx b/FrontEnd/src/app/views/common/FlatButton.tsx new file mode 100644 index 00000000..80bb654c --- /dev/null +++ b/FrontEnd/src/app/views/common/FlatButton.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import clsx from "clsx"; + +import { BootstrapThemeColor } from "@/common"; + +export interface FlatButtonProps { + variant?: BootstrapThemeColor | string; + disabled?: boolean; + className?: string; + style?: React.CSSProperties; + onClick?: () => void; +} + +const FlatButton: React.FC<FlatButtonProps> = (props) => { + const { disabled, className, style } = props; + const variant = props.variant ?? "primary"; + + const onClick = disabled ? undefined : props.onClick; + + return ( + <div + className={clsx( + "flat-button", + variant, + disabled ? "disabled" : null, + className + )} + style={style} + onClick={onClick} + > + {props.children} + </div> + ); +}; + +export default FlatButton; diff --git a/FrontEnd/src/app/views/common/Menu.tsx b/FrontEnd/src/app/views/common/Menu.tsx index 54650f22..d942d452 100644 --- a/FrontEnd/src/app/views/common/Menu.tsx +++ b/FrontEnd/src/app/views/common/Menu.tsx @@ -30,12 +30,13 @@ const Menu: React.FC<MenuProps> = ({ items, className, onItemClicked }) => { return ( <div className={clsx("cru-menu", className)}> - {items.map((item) => { + {items.map((item, index) => { if (item.type === "divider") { - return <div className="cru-menu-divider" />; + return <div key={index} className="cru-menu-divider" />; } else { return ( <div + key={index} className={clsx( "cru-menu-item", `color-${item.color ?? "primary"}` diff --git a/FrontEnd/src/app/views/common/TabPages.tsx b/FrontEnd/src/app/views/common/TabPages.tsx new file mode 100644 index 00000000..424e769f --- /dev/null +++ b/FrontEnd/src/app/views/common/TabPages.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import { Nav } from "react-bootstrap"; +import { useTranslation } from "react-i18next"; + +import { convertI18nText, I18nText, UiLogicError } from "@/common"; + +export interface TabPage { + id: string; + tabText: I18nText; + page: React.ReactNode; +} + +export interface TabPagesProps { + pages: TabPage[]; + actions?: React.ReactNode; + className?: string; + style?: React.CSSProperties; + navClassName?: string; + navStyle?: React.CSSProperties; + pageContainerClassName?: string; + pageContainerStyle?: React.CSSProperties; +} + +const TabPages: React.FC<TabPagesProps> = ({ + pages, + actions, + className, + style, + navClassName, + navStyle, + pageContainerClassName, + pageContainerStyle, +}) => { + if (pages.length === 0) { + throw new UiLogicError("Page list can't be empty."); + } + + const { t } = useTranslation(); + + const [tab, setTab] = React.useState<string>(pages[0].id); + + const currentPage = pages.find((p) => p.id === tab); + + if (currentPage == null) { + throw new UiLogicError("Current tab value is bad."); + } + + return ( + <div className={className} style={style}> + <Nav variant="tabs" className={navClassName} style={navStyle}> + {pages.map((page) => ( + <Nav.Item key={page.id}> + <Nav.Link + active={tab === page.id} + onClick={() => { + setTab(page.id); + }} + > + {convertI18nText(page.tabText, t)} + </Nav.Link> + </Nav.Item> + ))} + {actions != null && ( + <div className="ml-auto cru-tab-pages-action-area">{actions}</div> + )} + </Nav> + <div className={pageContainerClassName} style={pageContainerStyle}> + {currentPage.page} + </div> + </div> + ); +}; + +export default TabPages; diff --git a/FrontEnd/src/app/views/common/common.sass b/FrontEnd/src/app/views/common/common.sass index 0a30d995..f3022d19 100644 --- a/FrontEnd/src/app/views/common/common.sass +++ b/FrontEnd/src/app/views/common/common.sass @@ -92,3 +92,7 @@ .cru-menu-divider
border-top: 1px solid $gray-200
+
+.cru-tab-pages-action-area
+ display: flex
+ align-items: center
|