aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/views/common
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/views/common')
-rw-r--r--FrontEnd/src/views/common/AppBar.css95
-rw-r--r--FrontEnd/src/views/common/AppBar.tsx4
-rw-r--r--FrontEnd/src/views/common/ConfirmDialog.tsx40
-rw-r--r--FrontEnd/src/views/common/ImageCropper.css38
-rw-r--r--FrontEnd/src/views/common/ImageCropper.tsx2
-rw-r--r--FrontEnd/src/views/common/LoadingButton.tsx29
-rw-r--r--FrontEnd/src/views/common/LoadingPage.tsx5
-rw-r--r--FrontEnd/src/views/common/Menu.tsx26
-rw-r--r--FrontEnd/src/views/common/SearchInput.css4
-rw-r--r--FrontEnd/src/views/common/SearchInput.tsx22
-rw-r--r--FrontEnd/src/views/common/Spinner.tsx13
-rw-r--r--FrontEnd/src/views/common/ToggleIconButton.tsx30
-rw-r--r--FrontEnd/src/views/common/alert/AlertHost.tsx11
-rw-r--r--FrontEnd/src/views/common/alert/alert.css4
-rw-r--r--FrontEnd/src/views/common/alert/alert.sass15
-rw-r--r--FrontEnd/src/views/common/button/Button.css72
-rw-r--r--FrontEnd/src/views/common/button/Button.tsx33
-rw-r--r--FrontEnd/src/views/common/button/FlatButton.css8
-rw-r--r--FrontEnd/src/views/common/button/FlatButton.tsx31
-rw-r--r--FrontEnd/src/views/common/button/LoadingButton.tsx21
-rw-r--r--FrontEnd/src/views/common/button/TextButton.css8
-rw-r--r--FrontEnd/src/views/common/button/TextButton.tsx31
-rw-r--r--FrontEnd/src/views/common/button/common.ts31
-rw-r--r--FrontEnd/src/views/common/dailog/ConfirmDialog.tsx40
-rw-r--r--FrontEnd/src/views/common/dailog/Dialog.css0
-rw-r--r--FrontEnd/src/views/common/dailog/Dialog.tsx13
-rw-r--r--FrontEnd/src/views/common/dailog/FullPageDialog.tsx (renamed from FrontEnd/src/views/common/FullPage.tsx)6
-rw-r--r--FrontEnd/src/views/common/dailog/OperationDialog.tsx (renamed from FrontEnd/src/views/common/OperationDialog.tsx)114
-rw-r--r--FrontEnd/src/views/common/index.css135
-rw-r--r--FrontEnd/src/views/common/tab/TabPages.tsx (renamed from FrontEnd/src/views/common/TabPages.tsx)20
-rw-r--r--FrontEnd/src/views/common/tab/Tabs.tsx19
31 files changed, 492 insertions, 428 deletions
diff --git a/FrontEnd/src/views/common/AppBar.css b/FrontEnd/src/views/common/AppBar.css
new file mode 100644
index 00000000..16572817
--- /dev/null
+++ b/FrontEnd/src/views/common/AppBar.css
@@ -0,0 +1,95 @@
+.app-bar {
+ display: flex;
+ align-items: center;
+ height: 56px;
+ position: fixed;
+ z-index: 1030;
+ top: 0;
+ left: 0;
+ right: 0;
+ background-color: var(--tl-primary-color);
+ transition: background-color 1s;
+}
+
+.app-bar .cru-avatar {
+ background-color: white;
+}
+
+.app-bar a {
+ color: var(--tl-text-on-primary-r1-color);
+ text-decoration: none;
+ margin: 0 1em;
+ transition: color 1s;
+}
+.app-bar a:hover {
+ color: var(--tl-text-on-primary-color);
+}
+.app-bar a.active {
+ color: var(--tl-text-on-primary-color);
+}
+
+.app-bar-brand {
+ display: flex;
+ align-items: center;
+}
+
+.app-bar-brand-icon {
+ height: 2em;
+}
+
+.app-bar-main-area {
+ display: flex;
+ flex-grow: 1;
+}
+
+.app-bar-link-area {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+}
+
+.app-bar-user-area {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+ margin-left: auto;
+}
+
+.small-screen .app-bar-main-area {
+ position: absolute;
+ top: 56px;
+ left: 0;
+ right: 0;
+ transform-origin: top;
+ transition: transform 0.6s, background-color 1s;
+ background-color: var(--tl-primary-color);
+ flex-direction: column;
+}
+.small-screen .app-bar-main-area.app-bar-collapse {
+ transform: scale(1, 0);
+}
+.small-screen .app-bar-main-area a {
+ text-align: left;
+ padding: 0.5em 0.5em;
+}
+.small-screen .app-bar-link-area {
+ flex-direction: column;
+ align-items: stretch;
+}
+.small-screen .app-bar-user-area {
+ flex-direction: column;
+ align-items: stretch;
+ margin-left: unset;
+}
+.small-screen .app-bar-avatar {
+ align-self: flex-end;
+}
+
+.app-bar-toggler {
+ margin-left: auto;
+ font-size: 2em;
+ margin-right: 1em;
+ color: var(--tl-text-on-primary-color);
+ cursor: pointer;
+ user-select: none;
+}
diff --git a/FrontEnd/src/views/common/AppBar.tsx b/FrontEnd/src/views/common/AppBar.tsx
index ebc8bf0c..5d62a88d 100644
--- a/FrontEnd/src/views/common/AppBar.tsx
+++ b/FrontEnd/src/views/common/AppBar.tsx
@@ -9,7 +9,7 @@ import { useUser } from "@/services/user";
import TimelineLogo from "./TimelineLogo";
import UserAvatar from "./user/UserAvatar";
-import "./index.css";
+import "./AppBar.css";
const AppBar: React.FC = (_) => {
const { t } = useTranslation();
@@ -68,7 +68,7 @@ const AppBar: React.FC = (_) => {
"/",
<UserAvatar
username={user.username}
- className="avatar small rounded-circle bg-white cursor-pointer ml-auto"
+ className="cru-avatar small cru-round cursor-pointer ml-auto"
/>,
"app-bar-avatar"
)
diff --git a/FrontEnd/src/views/common/ConfirmDialog.tsx b/FrontEnd/src/views/common/ConfirmDialog.tsx
deleted file mode 100644
index 72940c51..00000000
--- a/FrontEnd/src/views/common/ConfirmDialog.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-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/views/common/ImageCropper.css b/FrontEnd/src/views/common/ImageCropper.css
new file mode 100644
index 00000000..2c4d0a8c
--- /dev/null
+++ b/FrontEnd/src/views/common/ImageCropper.css
@@ -0,0 +1,38 @@
+.image-cropper-container {
+ position: relative;
+ box-sizing: border-box;
+ user-select: none;
+}
+
+.image-cropper-container img {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.image-cropper-mask-container {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ overflow: hidden;
+}
+
+.image-cropper-mask {
+ position: absolute;
+ box-shadow: 0 0 0 10000px rgba(255, 255, 255, 0.8);
+ touch-action: none;
+}
+
+.image-cropper-handler {
+ position: absolute;
+ width: 26px;
+ height: 26px;
+ border: black solid 2px;
+ border-radius: 50%;
+ background: white;
+ touch-action: none;
+}
diff --git a/FrontEnd/src/views/common/ImageCropper.tsx b/FrontEnd/src/views/common/ImageCropper.tsx
index 2ef5b7ed..be44200a 100644
--- a/FrontEnd/src/views/common/ImageCropper.tsx
+++ b/FrontEnd/src/views/common/ImageCropper.tsx
@@ -3,6 +3,8 @@ import classnames from "classnames";
import { UiLogicError } from "@/common";
+import "./ImageCropper.css";
+
export interface Clip {
left: number;
top: number;
diff --git a/FrontEnd/src/views/common/LoadingButton.tsx b/FrontEnd/src/views/common/LoadingButton.tsx
deleted file mode 100644
index cd9f1adc..00000000
--- a/FrontEnd/src/views/common/LoadingButton.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from "react";
-import { Button, ButtonProps, Spinner } from "react-bootstrap";
-
-const LoadingButton: React.FC<{ loading?: boolean } & ButtonProps> = ({
- loading,
- variant,
- disabled,
- ...otherProps
-}) => {
- return (
- <Button
- variant={variant != null ? `outline-${variant}` : "outline-primary"}
- disabled={disabled || loading}
- {...otherProps}
- >
- {otherProps.children}
- {loading ? (
- <Spinner
- className="ms-1"
- variant={variant}
- animation="grow"
- size="sm"
- />
- ) : null}
- </Button>
- );
-};
-
-export default LoadingButton;
diff --git a/FrontEnd/src/views/common/LoadingPage.tsx b/FrontEnd/src/views/common/LoadingPage.tsx
index 590fafa0..8c1e681a 100644
--- a/FrontEnd/src/views/common/LoadingPage.tsx
+++ b/FrontEnd/src/views/common/LoadingPage.tsx
@@ -1,10 +1,11 @@
import React from "react";
-import { Spinner } from "react-bootstrap";
+
+import Spinner from "./Spinner";
const LoadingPage: React.FC = () => {
return (
<div className="position-fixed w-100 h-100 d-flex justify-content-center align-items-center">
- <Spinner variant="primary" animation="border" />
+ <Spinner />
</div>
);
};
diff --git a/FrontEnd/src/views/common/Menu.tsx b/FrontEnd/src/views/common/Menu.tsx
index ae73a331..a5d2ec2c 100644
--- a/FrontEnd/src/views/common/Menu.tsx
+++ b/FrontEnd/src/views/common/Menu.tsx
@@ -1,9 +1,9 @@
import React from "react";
import classnames from "classnames";
-import { OverlayTrigger, OverlayTriggerProps, Popover } from "react-bootstrap";
import { useTranslation } from "react-i18next";
-import { BootstrapThemeColor, convertI18nText, I18nText } from "@/common";
+import { convertI18nText, I18nText } from "@/common";
+import { PaletteColorType } from "@/palette";
export type MenuItem =
| {
@@ -13,7 +13,7 @@ export type MenuItem =
type: "button";
text: I18nText;
iconClassName?: string;
- color?: BootstrapThemeColor;
+ color?: PaletteColorType;
onClick: () => void;
};
@@ -67,26 +67,14 @@ export default Menu;
export interface PopupMenuProps {
items: MenuItems;
- children: OverlayTriggerProps["children"];
+ children: React.ReactElement;
}
export const PopupMenu: React.FC<PopupMenuProps> = ({ items, children }) => {
const [show, setShow] = React.useState<boolean>(false);
const toggle = (): void => setShow(!show);
- return (
- <OverlayTrigger
- trigger="click"
- rootClose
- overlay={
- <Popover id="menu-popover">
- <Menu items={items} onItemClicked={() => setShow(false)} />
- </Popover>
- }
- show={show}
- onToggle={toggle}
- >
- {children}
- </OverlayTrigger>
- );
+ // TODO:
+
+ return <Menu items={items} onItemClicked={() => setShow(false)} />;
};
diff --git a/FrontEnd/src/views/common/SearchInput.css b/FrontEnd/src/views/common/SearchInput.css
new file mode 100644
index 00000000..2943b3a2
--- /dev/null
+++ b/FrontEnd/src/views/common/SearchInput.css
@@ -0,0 +1,4 @@
+.cru-search-input {
+ display: flex;
+ flex-wrap: wrap;
+}
diff --git a/FrontEnd/src/views/common/SearchInput.tsx b/FrontEnd/src/views/common/SearchInput.tsx
index ccb6dad6..da3f1c19 100644
--- a/FrontEnd/src/views/common/SearchInput.tsx
+++ b/FrontEnd/src/views/common/SearchInput.tsx
@@ -1,7 +1,10 @@
import React, { useCallback } from "react";
import classnames from "classnames";
import { useTranslation } from "react-i18next";
-import { Spinner, Form, Button } from "react-bootstrap";
+
+import LoadingButton from "./button/LoadingButton";
+
+import "./SearchInput.css";
export interface SearchInputProps {
value: string;
@@ -38,14 +41,15 @@ const SearchInput: React.FC<SearchInputProps> = (props) => {
);
return (
- <Form
+ <div
className={classnames(
"cru-search-input",
alwaysOneline ? "flex-nowrap" : "flex-sm-nowrap",
props.className
)}
>
- <Form.Control
+ <input
+ type="text"
className="me-sm-2 flex-grow-1"
value={props.value}
onChange={onInputChange}
@@ -63,15 +67,11 @@ const SearchInput: React.FC<SearchInputProps> = (props) => {
"flex-shrink-0"
)}
>
- {props.loading ? (
- <Spinner variant="primary" animation="border" />
- ) : (
- <Button variant="outline-primary" onClick={props.onButtonClick}>
- {props.buttonText ?? t("search")}
- </Button>
- )}
+ <LoadingButton loading={props.loading} onClick={props.onButtonClick}>
+ {props.buttonText ?? t("search")}
+ </LoadingButton>
</div>
- </Form>
+ </div>
);
};
diff --git a/FrontEnd/src/views/common/Spinner.tsx b/FrontEnd/src/views/common/Spinner.tsx
new file mode 100644
index 00000000..783c9be2
--- /dev/null
+++ b/FrontEnd/src/views/common/Spinner.tsx
@@ -0,0 +1,13 @@
+import { PaletteColorType } from "@/palette";
+import React from "react";
+
+export interface SpinnerProps {
+ size?: "sm" | "md" | "lg" | number;
+ color?: PaletteColorType;
+}
+
+export default function Spinner(
+ props: SpinnerProps
+): React.ReactElement | null {
+ return <span />;
+}
diff --git a/FrontEnd/src/views/common/ToggleIconButton.tsx b/FrontEnd/src/views/common/ToggleIconButton.tsx
deleted file mode 100644
index c4d2d132..00000000
--- a/FrontEnd/src/views/common/ToggleIconButton.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from "react";
-import classnames from "classnames";
-
-export interface ToggleIconButtonProps
- extends React.HTMLAttributes<HTMLElement> {
- state: boolean;
- trueIconClassName: string;
- falseIconClassName: string;
-}
-
-const ToggleIconButton: React.FC<ToggleIconButtonProps> = ({
- state,
- className,
- trueIconClassName,
- falseIconClassName,
- ...otherProps
-}) => {
- return (
- <i
- className={classnames(
- state ? trueIconClassName : falseIconClassName,
- "icon-button",
- className
- )}
- {...otherProps}
- />
- );
-};
-
-export default ToggleIconButton;
diff --git a/FrontEnd/src/views/common/alert/AlertHost.tsx b/FrontEnd/src/views/common/alert/AlertHost.tsx
index 949be7ed..21b9882d 100644
--- a/FrontEnd/src/views/common/alert/AlertHost.tsx
+++ b/FrontEnd/src/views/common/alert/AlertHost.tsx
@@ -1,7 +1,6 @@
import React from "react";
import without from "lodash/without";
import { useTranslation } from "react-i18next";
-import { Alert } from "react-bootstrap";
import {
alertService,
@@ -52,13 +51,7 @@ export const AutoCloseAlert: React.FC<AutoCloseAlertProps> = (props) => {
};
return (
- <Alert
- className="m-3"
- variant={alert.type ?? "primary"}
- onClick={cancelTimer}
- onClose={close}
- dismissible
- >
+ <div className="m-3" onClick={cancelTimer}>
{(() => {
const { message } = alert;
if (typeof message === "function") {
@@ -66,7 +59,7 @@ export const AutoCloseAlert: React.FC<AutoCloseAlertProps> = (props) => {
return <Message />;
} else return convertI18nText(message, t);
})()}
- </Alert>
+ </div>
);
};
diff --git a/FrontEnd/src/views/common/alert/alert.css b/FrontEnd/src/views/common/alert/alert.css
new file mode 100644
index 00000000..12ce294e
--- /dev/null
+++ b/FrontEnd/src/views/common/alert/alert.css
@@ -0,0 +1,4 @@
+.alert-container {
+ position: fixed;
+ z-index: 1040;
+}
diff --git a/FrontEnd/src/views/common/alert/alert.sass b/FrontEnd/src/views/common/alert/alert.sass
deleted file mode 100644
index c3560b87..00000000
--- a/FrontEnd/src/views/common/alert/alert.sass
+++ /dev/null
@@ -1,15 +0,0 @@
-.alert-container
- position: fixed
- z-index: $zindex-popover
-
-@include media-breakpoint-up(sm)
- .alert-container
- bottom: 0
- right: 0
-
-@include media-breakpoint-down(sm)
- .alert-container
- bottom: 0
- right: 0
- left: 0
- text-align: center
diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css
new file mode 100644
index 00000000..b6df222f
--- /dev/null
+++ b/FrontEnd/src/views/common/button/Button.css
@@ -0,0 +1,72 @@
+.cru-button {
+ color: white;
+ cursor: pointer;
+ padding: 0.2em 0.5em;
+ border-radius: 0.2em;
+ border: none;
+ transition: all 0.6s;
+}
+
+.cru-button.primary {
+ background-color: var(--tl-primary-color);
+}
+
+.cru-button.primary:hover {
+ background-color: var(--tl-primary-f1-color);
+}
+
+.cru-button.primary:active {
+ background-color: var(--tl-primary-f2-color);
+}
+
+.cru-button.primary.disabled {
+ background-color: var(--tl-primary-f3-color);
+}
+
+.cru-button.secondary {
+ background-color: var(--tl-secondary-color);
+}
+
+.cru-button.secondary:hover {
+ background-color: var(--tl-secondary-f1-color);
+}
+
+.cru-button.secondary:active {
+ background-color: var(--tl-secondary-f2-color);
+}
+
+.cru-button.secondary.disabled {
+ background-color: var(--tl-secondary-f3-color);
+}
+
+.cru-button.success {
+ background-color: var(--tl-success-color);
+}
+
+.cru-button.success:hover {
+ background-color: var(--tl-success-f1-color);
+}
+
+.cru-button.success:active {
+ background-color: var(--tl-success-f2-color);
+}
+
+.cru-button.success.disabled {
+ background-color: var(--tl-success-f3-color);
+}
+
+.cru-button.danger {
+ background-color: var(--tl-danger-color);
+}
+
+.cru-button.danger:hover {
+ background-color: var(--tl-danger-f1-color);
+}
+
+.cru-button.danger:active {
+ background-color: var(--tl-danger-f2-color);
+}
+
+.cru-button.danger.disabled {
+ background-color: var(--tl-danger-f3-color);
+}
diff --git a/FrontEnd/src/views/common/button/Button.tsx b/FrontEnd/src/views/common/button/Button.tsx
new file mode 100644
index 00000000..a39ef8a7
--- /dev/null
+++ b/FrontEnd/src/views/common/button/Button.tsx
@@ -0,0 +1,33 @@
+import React from "react";
+import { useTranslation } from "react-i18next";
+
+import { calculateProps, CommonButtonProps } from "./common";
+
+import "./Button.css";
+
+function _Button(
+ props: CommonButtonProps & {
+ outline?: boolean;
+ customButtonClassName?: string;
+ },
+ ref: React.ForwardedRef<HTMLButtonElement>
+): React.ReactElement | null {
+ const { t } = useTranslation();
+
+ const { customButtonClassName, outline, ...otherProps } = props;
+
+ const { newProps, children } = calculateProps(
+ otherProps,
+ customButtonClassName ?? "cru-button" + (outline ? " outline" : ""),
+ t
+ );
+
+ return (
+ <button ref={ref} {...newProps}>
+ {children}
+ </button>
+ );
+}
+
+const Button = React.forwardRef(_Button);
+export default Button;
diff --git a/FrontEnd/src/views/common/button/FlatButton.css b/FrontEnd/src/views/common/button/FlatButton.css
index 522563b9..c3c0dbb3 100644
--- a/FrontEnd/src/views/common/button/FlatButton.css
+++ b/FrontEnd/src/views/common/button/FlatButton.css
@@ -20,7 +20,7 @@
}
.cru-flat-button.primary.disabled {
- color: var(--tl-primary-lighter-color);
+ color: var(--tl-primary-l1-color);
}
.cru-flat-button.secondary {
@@ -28,7 +28,7 @@
}
.cru-flat-button.secondary.disabled {
- color: var(--tl-secondary-lighter-color);
+ color: var(--tl-secondary-l1-color);
}
.cru-flat-button.success {
@@ -36,7 +36,7 @@
}
.cru-flat-button.success.disabled {
- color: var(--tl-success-lighter-color);
+ color: var(--tl-success-l1-color);
}
.cru-flat-button.danger {
@@ -44,5 +44,5 @@
}
.cru-flat-button.danger.disabled {
- color: var(--tl-danger-ligher-color);
+ color: var(--tl-danger-l1-color);
}
diff --git a/FrontEnd/src/views/common/button/FlatButton.tsx b/FrontEnd/src/views/common/button/FlatButton.tsx
index 6351971a..266ea908 100644
--- a/FrontEnd/src/views/common/button/FlatButton.tsx
+++ b/FrontEnd/src/views/common/button/FlatButton.tsx
@@ -1,39 +1,16 @@
import React from "react";
-import { useTranslation } from "react-i18next";
-import classNames from "classnames";
-import { convertI18nText, I18nText } from "@/common";
-import { PaletteColorType } from "@/palette";
+import { CommonButtonProps } from "./common";
+import Button from "./Button";
import "./FlatButton.css";
function _FlatButton(
- {
- text,
- color,
- onClick,
- className,
- style,
- }: {
- text: I18nText;
- color?: PaletteColorType;
- onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
- className?: string;
- style?: React.CSSProperties;
- },
+ props: CommonButtonProps,
ref: React.ForwardedRef<HTMLButtonElement>
): React.ReactElement | null {
- const { t } = useTranslation();
-
return (
- <button
- ref={ref}
- className={classNames("cru-flat-button", color ?? "primary", className)}
- onClick={onClick}
- style={style}
- >
- {convertI18nText(text, t)}
- </button>
+ <Button ref={ref} customButtonClassName="cru-flat-button" {...props} />
);
}
diff --git a/FrontEnd/src/views/common/button/LoadingButton.tsx b/FrontEnd/src/views/common/button/LoadingButton.tsx
new file mode 100644
index 00000000..aee83aa2
--- /dev/null
+++ b/FrontEnd/src/views/common/button/LoadingButton.tsx
@@ -0,0 +1,21 @@
+import React from "react";
+
+import { CommonButtonProps } from "./common";
+import Button from "./Button";
+import Spinner from "../Spinner";
+
+const LoadingButton: React.FC<{ loading?: boolean } & CommonButtonProps> = ({
+ loading,
+ disabled,
+ color,
+ ...otherProps
+}) => {
+ return (
+ <Button color={color} disabled={disabled || loading} {...otherProps}>
+ {otherProps.children}
+ {loading ? <Spinner color={color} /> : null}
+ </Button>
+ );
+};
+
+export default LoadingButton;
diff --git a/FrontEnd/src/views/common/button/TextButton.css b/FrontEnd/src/views/common/button/TextButton.css
index dc5abaaa..d267fb38 100644
--- a/FrontEnd/src/views/common/button/TextButton.css
+++ b/FrontEnd/src/views/common/button/TextButton.css
@@ -8,7 +8,7 @@
}
.cru-text-button.primary:hover {
- color: var(--tl-primary-lighter-color);
+ color: var(--tl-primary-l1-color);
}
.cru-text-button.secondary {
@@ -16,7 +16,7 @@
}
.cru-text-button.secondary:hover {
- color: var(--tl-secondary-lighter-color);
+ color: var(--tl-secondary-l1-color);
}
.cru-text-button.success {
@@ -24,7 +24,7 @@
}
.cru-text-button.success:hover {
- color: var(--tl-success-lighter-color);
+ color: var(--tl-success-l1-color);
}
.cru-text-button.danger {
@@ -32,5 +32,5 @@
}
.cru-text-button.danger:hover {
- color: var(--tl-danger-lighter-color);
+ color: var(--tl-danger-l1-color);
}
diff --git a/FrontEnd/src/views/common/button/TextButton.tsx b/FrontEnd/src/views/common/button/TextButton.tsx
index 1a2bac94..1d8e7a4b 100644
--- a/FrontEnd/src/views/common/button/TextButton.tsx
+++ b/FrontEnd/src/views/common/button/TextButton.tsx
@@ -1,39 +1,16 @@
import React from "react";
-import { useTranslation } from "react-i18next";
-import classNames from "classnames";
-import { convertI18nText, I18nText } from "@/common";
-import { PaletteColorType } from "@/palette";
+import { CommonButtonProps } from "./common";
+import Button from "./Button";
import "./TextButton.css";
function _TextButton(
- {
- text,
- color,
- onClick,
- className,
- style,
- }: {
- text: I18nText;
- color?: PaletteColorType;
- onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
- className?: string;
- style?: React.CSSProperties;
- },
+ props: CommonButtonProps,
ref: React.ForwardedRef<HTMLButtonElement>
): React.ReactElement | null {
- const { t } = useTranslation();
-
return (
- <button
- ref={ref}
- className={classNames("cru-text-button", color ?? "primary", className)}
- onClick={onClick}
- style={style}
- >
- {convertI18nText(text, t)}
- </button>
+ <Button ref={ref} customButtonClassName="cru-flat-button" {...props} />
);
}
diff --git a/FrontEnd/src/views/common/button/common.ts b/FrontEnd/src/views/common/button/common.ts
new file mode 100644
index 00000000..a9db959e
--- /dev/null
+++ b/FrontEnd/src/views/common/button/common.ts
@@ -0,0 +1,31 @@
+import React from "react";
+import classNames from "classnames";
+import { TFunction } from "i18next";
+
+import { convertI18nText, I18nText } from "@/common";
+import { PaletteColorType } from "@/palette";
+
+export type CommonButtonProps = {
+ text?: I18nText;
+ color?: PaletteColorType;
+} & React.ButtonHTMLAttributes<HTMLButtonElement>;
+
+export function calculateProps(
+ props: CommonButtonProps,
+ buttonClassName: string,
+ t: TFunction
+): {
+ children: React.ReactNode;
+ newProps: React.ButtonHTMLAttributes<HTMLButtonElement>;
+} {
+ const { text, color, className, children, ...otherProps } = props;
+ const newProps = {
+ className: classNames(buttonClassName, color ?? "primary", className),
+ ...otherProps,
+ };
+
+ return {
+ children: text != null ? convertI18nText(text, t) : children,
+ newProps: newProps,
+ };
+}
diff --git a/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx b/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx
new file mode 100644
index 00000000..1ad52350
--- /dev/null
+++ b/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx
@@ -0,0 +1,40 @@
+import { convertI18nText, I18nText } from "@/common";
+import React from "react";
+import { useTranslation } from "react-i18next";
+
+import Button from "../button/Button";
+import Dialog from "./Dialog";
+
+const ConfirmDialog: React.FC<{
+ open?: boolean;
+ onClose: () => void;
+ onConfirm: () => void;
+ title: I18nText;
+ body: I18nText;
+}> = ({ open, onClose, onConfirm, title, body }) => {
+ const { t } = useTranslation();
+
+ return (
+ <Dialog onClose={onClose} open={open}>
+ <h3 className="text-danger">{convertI18nText(title, t)}</h3>
+ <p>{convertI18nText(body, t)}</p>
+ <div>
+ <Button
+ text="operationDialog.cancel"
+ color="secondary"
+ onClick={onClose}
+ />
+ <Button
+ text="operationDialog.confirm"
+ color="danger"
+ onClick={() => {
+ onConfirm();
+ onClose();
+ }}
+ />
+ </div>
+ </Dialog>
+ );
+};
+
+export default ConfirmDialog;
diff --git a/FrontEnd/src/views/common/dailog/Dialog.css b/FrontEnd/src/views/common/dailog/Dialog.css
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/FrontEnd/src/views/common/dailog/Dialog.css
diff --git a/FrontEnd/src/views/common/dailog/Dialog.tsx b/FrontEnd/src/views/common/dailog/Dialog.tsx
new file mode 100644
index 00000000..5a3902c4
--- /dev/null
+++ b/FrontEnd/src/views/common/dailog/Dialog.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+
+export interface DialogProps {
+ onClose: () => void;
+ open?: boolean;
+ children?: React.ReactNode;
+}
+
+export default function Dialog(props: DialogProps): React.ReactElement | null {
+ const { open, onClose, children } = props;
+
+ return <div>{children}</div>;
+}
diff --git a/FrontEnd/src/views/common/FullPage.tsx b/FrontEnd/src/views/common/dailog/FullPageDialog.tsx
index 1b59045a..88c90bbc 100644
--- a/FrontEnd/src/views/common/FullPage.tsx
+++ b/FrontEnd/src/views/common/dailog/FullPageDialog.tsx
@@ -1,13 +1,13 @@
import React from "react";
import classnames from "classnames";
-export interface FullPageProps {
+export interface FullPageDialogProps {
show: boolean;
onBack: () => void;
contentContainerClassName?: string;
}
-const FullPage: React.FC<FullPageProps> = ({
+const FullPageDialog: React.FC<FullPageDialogProps> = ({
show,
onBack,
children,
@@ -36,4 +36,4 @@ const FullPage: React.FC<FullPageProps> = ({
);
};
-export default FullPage;
+export default FullPageDialog;
diff --git a/FrontEnd/src/views/common/OperationDialog.tsx b/FrontEnd/src/views/common/dailog/OperationDialog.tsx
index ac4c51b9..1e765276 100644
--- a/FrontEnd/src/views/common/OperationDialog.tsx
+++ b/FrontEnd/src/views/common/dailog/OperationDialog.tsx
@@ -1,12 +1,15 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
-import { Form, Button, Modal } from "react-bootstrap";
import { TwitterPicker } from "react-color";
import moment from "moment";
import { convertI18nText, I18nText, UiLogicError } from "@/common";
-import LoadingButton from "./LoadingButton";
+import { PaletteColorType } from "@/palette";
+
+import Button from "../button/Button";
+import LoadingButton from "../button/LoadingButton";
+import Dialog from "./Dialog";
interface DefaultErrorPromptProps {
error?: string;
@@ -141,9 +144,9 @@ export interface OperationDialogProps<
OperationInputInfoList extends readonly OperationDialogInput[]
> {
open: boolean;
- close: () => void;
+ onClose: () => void;
title: I18nText | (() => React.ReactNode);
- themeColor?: "danger" | "success" | string;
+ themeColor?: PaletteColorType;
onProcess: (
inputs: MapOperationInputInfoValueTypeList<OperationInputInfoList>
) => Promise<TData>;
@@ -204,7 +207,7 @@ const OperationDialog = <
const close = (): void => {
if (step.type !== "process") {
- props.close();
+ props.onClose();
if (step.type === "success" && props.onSuccessAndClose) {
props.onSuccessAndClose(step.data);
}
@@ -278,7 +281,7 @@ const OperationDialog = <
body = (
<>
- <Modal.Body>
+ <div>
{inputPrompt}
{inputScheme.map((item, index) => {
const value = values[index];
@@ -289,50 +292,42 @@ const OperationDialog = <
if (item.type === "text") {
return (
- <Form.Group key={index}>
+ <div key={index}>
{item.label && (
- <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
+ <label>{convertI18nText(item.label, t)}</label>
)}
- <Form.Control
+ <input
type={item.password === true ? "password" : "text"}
value={value as string}
onChange={(e) => {
const v = e.target.value;
updateValue(index, v);
}}
- isInvalid={error != null}
disabled={process}
/>
- {error != null && (
- <Form.Control.Feedback type="invalid">
- {error}
- </Form.Control.Feedback>
- )}
- {item.helperText && (
- <Form.Text>{t(item.helperText)}</Form.Text>
- )}
- </Form.Group>
+ {error != null && <div>{error}</div>}
+ {item.helperText && <div>{t(item.helperText)}</div>}
+ </div>
);
} else if (item.type === "bool") {
return (
- <Form.Group key={index}>
- <Form.Check<"input">
+ <div key={index}>
+ <input
type="checkbox"
checked={value as boolean}
onChange={(event) => {
updateValue(index, event.currentTarget.checked);
}}
- label={convertI18nText(item.label, t)}
disabled={process}
/>
- </Form.Group>
+ <label>{convertI18nText(item.label, t)}</label>
+ </div>
);
} else if (item.type === "select") {
return (
- <Form.Group key={index}>
- <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
- <Form.Control
- as="select"
+ <div key={index}>
+ <label>{convertI18nText(item.label, t)}</label>
+ <select
value={value as string}
onChange={(event) => {
updateValue(index, event.target.value);
@@ -347,14 +342,14 @@ const OperationDialog = <
</option>
);
})}
- </Form.Control>
- </Form.Group>
+ </select>
+ </div>
);
} else if (item.type === "color") {
return (
- <Form.Group key={index}>
+ <div key={index}>
{item.canBeNull ? (
- <Form.Check<"input">
+ <input
type="checkbox"
checked={value !== null}
onChange={(event) => {
@@ -364,52 +359,47 @@ const OperationDialog = <
updateValue(index, null);
}
}}
- label={convertI18nText(item.label, t)}
disabled={process}
/>
- ) : (
- <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
- )}
+ ) : null}
+ <label>{convertI18nText(item.label, t)}</label>
{value !== null && (
<TwitterPicker
color={value as string}
onChange={(result) => updateValue(index, result.hex)}
/>
)}
- </Form.Group>
+ </div>
);
} else if (item.type === "datetime") {
return (
- <Form.Group key={index}>
+ <div key={index}>
{item.label && (
- <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
+ <label>{convertI18nText(item.label, t)}</label>
)}
- <Form.Control
+ <input
type="datetime-local"
value={value as string}
onChange={(e) => {
const v = e.target.value;
updateValue(index, v);
}}
- isInvalid={error != null}
disabled={process}
/>
- {error != null && (
- <Form.Control.Feedback type="invalid">
- {error}
- </Form.Control.Feedback>
- )}
- </Form.Group>
+ {error != null && <div>{error}</div>}
+ </div>
);
}
})}
- </Modal.Body>
- <Modal.Footer>
- <Button variant="outline-secondary" onClick={close}>
- {t("operationDialog.cancel")}
- </Button>
+ </div>
+ <div>
+ <Button
+ text="operationDialog.cancel"
+ color="secondary"
+ onClick={close}
+ />
<LoadingButton
- variant={props.themeColor}
+ color={props.themeColor}
loading={process}
disabled={!canProcess}
onClick={() => {
@@ -421,7 +411,7 @@ const OperationDialog = <
>
{t("operationDialog.confirm")}
</LoadingButton>
- </Modal.Footer>
+ </div>
</>
);
} else {
@@ -439,12 +429,10 @@ const OperationDialog = <
}
body = (
<>
- <Modal.Body>{content}</Modal.Body>
- <Modal.Footer>
- <Button variant="primary" onClick={close}>
- {t("operationDialog.ok")}
- </Button>
- </Modal.Footer>
+ <div>{content}</div>
+ <div>
+ <Button text="operationDialog.ok" color="primary" onClick={close} />
+ </div>
</>
);
}
@@ -455,16 +443,16 @@ const OperationDialog = <
: convertI18nText(props.title, t);
return (
- <Modal show={props.open} onHide={close}>
- <Modal.Header
+ <Dialog open={props.open} onClose={close}>
+ <h3
className={
props.themeColor != null ? "text-" + props.themeColor : undefined
}
>
{title}
- </Modal.Header>
+ </h3>
{body}
- </Modal>
+ </Dialog>
);
};
diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css
index bfd82b58..aaaaad58 100644
--- a/FrontEnd/src/views/common/index.css
+++ b/FrontEnd/src/views/common/index.css
@@ -1,130 +1,20 @@
-.image-cropper-container {
- position: relative;
- box-sizing: border-box;
- user-select: none;
+.cru-avatar {
+ width: 60px;
+ height: 60px;
}
-.image-cropper-container img {
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
+.cru-avatar.large {
+ width: 100px;
+ height: 100px;
}
-.image-cropper-mask-container {
- position: absolute;
- left: 0;
- top: 0;
- right: 0;
- bottom: 0;
- overflow: hidden;
+.cru-avatar.small {
+ width: 40px;
+ height: 40px;
}
-.image-cropper-mask {
- position: absolute;
- box-shadow: 0 0 0 10000px rgba(255, 255, 255, 0.8);
- touch-action: none;
-}
-
-.image-cropper-handler {
- position: absolute;
- width: 26px;
- height: 26px;
- border: black solid 2px;
+.cru-round {
border-radius: 50%;
- background: white;
- touch-action: none;
-}
-
-.app-bar {
- display: flex;
- align-items: center;
- height: 56px;
- position: fixed;
- z-index: 1030;
- top: 0;
- left: 0;
- right: 0;
- background-color: var(--tl-primary-color);
- transition: background-color 1s;
-}
-.app-bar a {
- color: var(--tl-text-on-primary-inactive-color);
- text-decoration: none;
- margin: 0 1em;
-}
-.app-bar a:hover {
- color: var(--tl-text-on-primary-color);
-}
-.app-bar a.active {
- color: var(--tl-text-on-primary-color);
-}
-
-.app-bar-brand {
- display: flex;
- align-items: center;
-}
-
-.app-bar-brand-icon {
- height: 2em;
-}
-
-.app-bar-main-area {
- display: flex;
- flex-grow: 1;
-}
-
-.app-bar-link-area {
- display: flex;
- align-items: center;
- flex-shrink: 0;
-}
-
-.app-bar-user-area {
- display: flex;
- align-items: center;
- flex-shrink: 0;
- margin-left: auto;
-}
-
-.small-screen .app-bar-main-area {
- position: absolute;
- top: 56px;
- left: 0;
- right: 0;
- transform-origin: top;
- transition: transform 0.6s, background-color 1s;
- background-color: var(--tl-primary-color);
- flex-direction: column;
-}
-.small-screen .app-bar-main-area.app-bar-collapse {
- transform: scale(1, 0);
-}
-.small-screen .app-bar-main-area a {
- text-align: left;
- padding: 0.5em 0.5em;
-}
-.small-screen .app-bar-link-area {
- flex-direction: column;
- align-items: stretch;
-}
-.small-screen .app-bar-user-area {
- flex-direction: column;
- align-items: stretch;
- margin-left: unset;
-}
-.small-screen .app-bar-avatar {
- align-self: flex-end;
-}
-
-.app-bar-toggler {
- margin-left: auto;
- font-size: 2em;
- margin-right: 1em;
- color: var(--tl-text-on-primary-color);
- cursor: pointer;
- user-select: none;
}
.cru-skeleton {
@@ -247,11 +137,6 @@
align-items: center;
}
-.cru-search-input {
- display: flex;
- flex-wrap: wrap;
-}
-
.alert-container {
position: fixed;
z-index: 1070;
diff --git a/FrontEnd/src/views/common/TabPages.tsx b/FrontEnd/src/views/common/tab/TabPages.tsx
index 2b1d91cb..b7a9fb36 100644
--- a/FrontEnd/src/views/common/TabPages.tsx
+++ b/FrontEnd/src/views/common/tab/TabPages.tsx
@@ -1,5 +1,4 @@
import React from "react";
-import { Nav } from "react-bootstrap";
import { useTranslation } from "react-i18next";
import { convertI18nText, I18nText, UiLogicError } from "@/common";
@@ -31,6 +30,8 @@ const TabPages: React.FC<TabPagesProps> = ({
pageContainerClassName,
pageContainerStyle,
}) => {
+ // TODO:
+
if (pages.length === 0) {
throw new UiLogicError("Page list can't be empty.");
}
@@ -47,23 +48,6 @@ const TabPages: React.FC<TabPagesProps> = ({
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="ms-auto cru-tab-pages-action-area">{actions}</div>
- )}
- </Nav>
<div className={pageContainerClassName} style={pageContainerStyle}>
{currentPage.page}
</div>
diff --git a/FrontEnd/src/views/common/tab/Tabs.tsx b/FrontEnd/src/views/common/tab/Tabs.tsx
new file mode 100644
index 00000000..29ebcbd8
--- /dev/null
+++ b/FrontEnd/src/views/common/tab/Tabs.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+
+import { I18nText } from "@/common";
+
+export interface Tab {
+ name: string;
+ text: I18nText;
+ link?: string;
+ onClick?: () => void;
+}
+
+export interface TabsProps {
+ activeTabName?: string;
+ tabs: Tab[];
+}
+
+export default function Tabs(props: TabsProps): React.ReactElement | null {
+ return <div></div>;
+}