aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/views
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/views')
-rw-r--r--FrontEnd/src/views/common/AppBar.css87
-rw-r--r--FrontEnd/src/views/common/AppBar.tsx98
-rw-r--r--FrontEnd/src/views/common/BlobImage.tsx26
-rw-r--r--FrontEnd/src/views/common/Card.css20
-rw-r--r--FrontEnd/src/views/common/Card.tsx38
-rw-r--r--FrontEnd/src/views/common/Icon.css3
-rw-r--r--FrontEnd/src/views/common/Icon.tsx30
-rw-r--r--FrontEnd/src/views/common/ImageCropper.css38
-rw-r--r--FrontEnd/src/views/common/ImageCropper.tsx312
-rw-r--r--FrontEnd/src/views/common/LoadFailReload.tsx37
-rw-r--r--FrontEnd/src/views/common/LoadingPage.tsx13
-rw-r--r--FrontEnd/src/views/common/Page.tsx15
-rw-r--r--FrontEnd/src/views/common/SearchInput.css8
-rw-r--r--FrontEnd/src/views/common/SearchInput.tsx50
-rw-r--r--FrontEnd/src/views/common/Skeleton.css14
-rw-r--r--FrontEnd/src/views/common/Skeleton.tsx32
-rw-r--r--FrontEnd/src/views/common/Spinner.css13
-rw-r--r--FrontEnd/src/views/common/Spinner.tsx36
-rw-r--r--FrontEnd/src/views/common/TimelineLogo.tsx27
-rw-r--r--FrontEnd/src/views/common/alert/AlertHost.tsx113
-rw-r--r--FrontEnd/src/views/common/alert/alert.css33
-rw-r--r--FrontEnd/src/views/common/breakpoints.ts3
-rw-r--r--FrontEnd/src/views/common/button/Button.css64
-rw-r--r--FrontEnd/src/views/common/button/Button.tsx46
-rw-r--r--FrontEnd/src/views/common/button/ButtonRow.css0
-rw-r--r--FrontEnd/src/views/common/button/ButtonRow.tsx62
-rw-r--r--FrontEnd/src/views/common/button/ButtonRowV2.tsx143
-rw-r--r--FrontEnd/src/views/common/button/FlatButton.css27
-rw-r--r--FrontEnd/src/views/common/button/FlatButton.tsx36
-rw-r--r--FrontEnd/src/views/common/button/IconButton.css30
-rw-r--r--FrontEnd/src/views/common/button/IconButton.tsx30
-rw-r--r--FrontEnd/src/views/common/button/LoadingButton.css13
-rw-r--r--FrontEnd/src/views/common/button/LoadingButton.tsx40
-rw-r--r--FrontEnd/src/views/common/button/index.tsx15
-rw-r--r--FrontEnd/src/views/common/common.ts14
-rw-r--r--FrontEnd/src/views/common/dialog/ConfirmDialog.css0
-rw-r--r--FrontEnd/src/views/common/dialog/ConfirmDialog.tsx59
-rw-r--r--FrontEnd/src/views/common/dialog/Dialog.css60
-rw-r--r--FrontEnd/src/views/common/dialog/Dialog.tsx63
-rw-r--r--FrontEnd/src/views/common/dialog/DialogContainer.css20
-rw-r--r--FrontEnd/src/views/common/dialog/DialogContainer.tsx95
-rw-r--r--FrontEnd/src/views/common/dialog/FullPageDialog.css44
-rw-r--r--FrontEnd/src/views/common/dialog/FullPageDialog.tsx53
-rw-r--r--FrontEnd/src/views/common/dialog/OperationDialog.css8
-rw-r--r--FrontEnd/src/views/common/dialog/OperationDialog.tsx228
-rw-r--r--FrontEnd/src/views/common/dialog/index.ts64
-rw-r--r--FrontEnd/src/views/common/hooks.ts14
-rw-r--r--FrontEnd/src/views/common/index.css100
-rw-r--r--FrontEnd/src/views/common/input/InputGroup.css54
-rw-r--r--FrontEnd/src/views/common/input/InputGroup.tsx461
-rw-r--r--FrontEnd/src/views/common/list/ListContainer.css4
-rw-r--r--FrontEnd/src/views/common/list/ListContainer.tsx23
-rw-r--r--FrontEnd/src/views/common/list/ListItemContainer.css3
-rw-r--r--FrontEnd/src/views/common/list/ListItemContainer.tsx23
-rw-r--r--FrontEnd/src/views/common/list/index.ts4
-rw-r--r--FrontEnd/src/views/common/menu/Menu.css36
-rw-r--r--FrontEnd/src/views/common/menu/Menu.tsx62
-rw-r--r--FrontEnd/src/views/common/menu/PopupMenu.css7
-rw-r--r--FrontEnd/src/views/common/menu/PopupMenu.tsx73
-rw-r--r--FrontEnd/src/views/common/tab/TabPages.tsx71
-rw-r--r--FrontEnd/src/views/common/tab/Tabs.css33
-rw-r--r--FrontEnd/src/views/common/tab/Tabs.tsx62
-rw-r--r--FrontEnd/src/views/common/theme-color.css173
-rw-r--r--FrontEnd/src/views/common/theme.css146
-rw-r--r--FrontEnd/src/views/common/user/UserAvatar.tsx22
65 files changed, 0 insertions, 3631 deletions
diff --git a/FrontEnd/src/views/common/AppBar.css b/FrontEnd/src/views/common/AppBar.css
deleted file mode 100644
index a0d975b5..00000000
--- a/FrontEnd/src/views/common/AppBar.css
+++ /dev/null
@@ -1,87 +0,0 @@
-.app-bar {
- height: 56px;
- position: fixed;
- z-index: 1030;
- top: 0;
- left: 0;
- right: 0;
- background-color: var(--cru-primary-color);
-}
-
-.app-bar {
- display: flex;
-}
-
-.app-bar .app-bar-brand {
- display: flex;
- align-items: center;
-}
-
-.app-bar .app-bar-brand-icon {
- height: 2em;
-}
-
-.app-bar .app-bar-user-area {
- display: flex;
- margin-left: auto;
-}
-
-.app-bar a {
- background-color: var(--cru-primary-color);
- color: var(--cru-push-button-text-color);
- text-decoration: none;
- display: flex;
- align-items: center;
- padding: 0 1em;
- transition: all 0.5s;
-}
-
-.app-bar a:hover {
- background-color: var(--cru-clickable-primary-hover-color);
-}
-
-.app-bar a:focus {
- background-color: var(--cru-clickable-primary-focus-color);
-}
-
-.app-bar a:active {
- background-color: var(--cru-clickable-primary-active-color);
-}
-
-/* the current page */
-.app-bar a.active {
- background-color: var(--cru-clickable-primary-focus-color);
-}
-
-.app-bar .app-bar-avatar img {
- width: 45px;
- height: 45px;
- background-color: white;
- border-radius: 50%;
-}
-
-.app-bar.desktop .app-bar-link-area {
- display: flex;
-}
-
-.app-bar.mobile .app-bar-link-area {
- position: absolute;
- z-index: -1;
- top: 56px;
- left: 0;
- right: 0;
- transition: transform 0.5s;
-}
-
-.app-bar.mobile a {
- height: 56px;
-}
-
-.app-bar.mobile.collapse .app-bar-link-area {
- transform: translateY(-100%);
-}
-
-.app-bar .toggler {
- font-size: 2em;
- margin-right: 0.5em;
-} \ No newline at end of file
diff --git a/FrontEnd/src/views/common/AppBar.tsx b/FrontEnd/src/views/common/AppBar.tsx
deleted file mode 100644
index b9ea825b..00000000
--- a/FrontEnd/src/views/common/AppBar.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import { useState } from "react";
-import classnames from "classnames";
-import { Link, NavLink } from "react-router-dom";
-
-import { I18nText, useC, useMobile } from "./common";
-import { useUser } from "@/services/user";
-
-import TimelineLogo from "./TimelineLogo";
-import { IconButton } from "./button";
-import UserAvatar from "./user/UserAvatar";
-
-import "./AppBar.css";
-
-function AppBarNavLink({
- link,
- className,
- label,
- onClick,
- children,
-}: {
- link: string;
- className?: string;
- label?: I18nText;
- onClick?: () => void;
- children?: React.ReactNode;
-}) {
- if (label != null && children != null) {
- throw new Error("AppBarNavLink: label and children cannot be both set");
- }
-
- const c = useC();
-
- return (
- <NavLink
- to={link}
- className={({ isActive }) => classnames(className, isActive && "active")}
- onClick={onClick}
- >
- {children != null ? children : c(label)}
- </NavLink>
- );
-}
-
-export default function AppBar() {
- const isMobile = useMobile();
-
- const [isCollapse, setIsCollapse] = useState<boolean>(true);
- const collapse = isMobile ? () => setIsCollapse(true) : undefined;
- const toggleCollapse = () => setIsCollapse(!isCollapse);
-
- const user = useUser();
- const hasAdministrationPermission = user && user.hasAdministrationPermission;
-
- return (
- <nav
- className={classnames(
- isMobile ? "mobile" : "desktop",
- "app-bar",
- isCollapse && "collapse",
- )}
- >
- <Link to="/" className="app-bar-brand" onClick={collapse}>
- <TimelineLogo className="app-bar-brand-icon" />
- Timeline
- </Link>
-
- <div className="app-bar-link-area">
- <AppBarNavLink
- link="/settings"
- label="nav.settings"
- onClick={collapse}
- />
- <AppBarNavLink link="/about" label="nav.about" onClick={collapse} />
- {hasAdministrationPermission && (
- <AppBarNavLink
- link="/admin"
- label="nav.administration"
- onClick={collapse}
- />
- )}
- </div>
-
- <div className="app-bar-user-area">
- {user != null ? (
- <AppBarNavLink link="/" className="app-bar-avatar" onClick={collapse}>
- <UserAvatar username={user.username} />
- </AppBarNavLink>
- ) : (
- <AppBarNavLink link="/login" label="nav.login" onClick={collapse} />
- )}
- </div>
-
- {isMobile && (
- <IconButton icon="list" className="toggler" onClick={toggleCollapse} />
- )}
- </nav>
- );
-}
diff --git a/FrontEnd/src/views/common/BlobImage.tsx b/FrontEnd/src/views/common/BlobImage.tsx
deleted file mode 100644
index 259c2210..00000000
--- a/FrontEnd/src/views/common/BlobImage.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { ComponentPropsWithoutRef, useState, useEffect } from "react";
-
-type BlobImageProps = Omit<ComponentPropsWithoutRef<"img">, "src"> & {
- imgRef?: React.Ref<HTMLImageElement>;
- src?: Blob | string | null;
-};
-
-export default function BlobImage(props: BlobImageProps) {
- const { imgRef, src, ...otherProps } = props;
-
- const [url, setUrl] = useState<string | null | undefined>(undefined);
-
- useEffect(() => {
- if (src instanceof Blob) {
- const url = URL.createObjectURL(src);
- setUrl(url);
- return () => {
- URL.revokeObjectURL(url);
- };
- } else {
- setUrl(src);
- }
- }, [src]);
-
- return <img ref={imgRef} {...otherProps} src={url ?? undefined} />;
-}
diff --git a/FrontEnd/src/views/common/Card.css b/FrontEnd/src/views/common/Card.css
deleted file mode 100644
index 6d655eb9..00000000
--- a/FrontEnd/src/views/common/Card.css
+++ /dev/null
@@ -1,20 +0,0 @@
-.cru-card {
- border-radius: var(--cru-card-border-radius);
- transition: all 0.3s;
-}
-
-.cru-card-background-none {
- background-color: transparent;
-}
-
-.cru-card-background-solid {
- background-color: var(--cru-background-color);
-}
-
-.cru-card-background-grayscale {
- background-color: var(--cru-container-background-color);
-}
-
-.cru-card-border-color {
- border: 2px solid var(--cru-card-border-color);
-}
diff --git a/FrontEnd/src/views/common/Card.tsx b/FrontEnd/src/views/common/Card.tsx
deleted file mode 100644
index a8f0d3cc..00000000
--- a/FrontEnd/src/views/common/Card.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { ComponentPropsWithoutRef, Ref } from "react";
-import classNames from "classnames";
-
-import { ThemeColor } from "./common";
-import "./Card.css";
-
-interface CardProps extends ComponentPropsWithoutRef<"div"> {
- containerRef?: Ref<HTMLDivElement>;
- color?: ThemeColor;
- border?: "color" | "none";
- background?: "color" | "solid" | "grayscale" | "none";
-}
-
-export default function Card({
- color,
- background,
- border,
- className,
- children,
- containerRef,
- ...otherProps
-}: CardProps) {
- return (
- <div
- ref={containerRef}
- className={classNames(
- "cru-card",
- `cru-card-${color ?? "primary"}`,
- `cru-card-border-${border ?? "color"}`,
- `cru-card-background-${background ?? "solid"}`,
- className,
- )}
- {...otherProps}
- >
- {children}
- </div>
- );
-}
diff --git a/FrontEnd/src/views/common/Icon.css b/FrontEnd/src/views/common/Icon.css
deleted file mode 100644
index fe980d7b..00000000
--- a/FrontEnd/src/views/common/Icon.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.cru-icon {
- font-size: 1.4rem;
-}
diff --git a/FrontEnd/src/views/common/Icon.tsx b/FrontEnd/src/views/common/Icon.tsx
deleted file mode 100644
index 2ac3a7ca..00000000
--- a/FrontEnd/src/views/common/Icon.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { ComponentPropsWithoutRef } from "react";
-import classNames from "classnames";
-
-import { ThemeColor } from "./common";
-
-import "./Icon.css";
-
-interface IconButtonProps extends ComponentPropsWithoutRef<"i"> {
- icon: string;
- color?: ThemeColor | "on-surface";
- size?: string | number;
-}
-
-export default function Icon(props: IconButtonProps) {
- const { icon, color, size, style, className, ...otherProps } = props;
-
- const colorName = color === "on-surface" ? "surface-on" : color;
-
- return (
- <i
- style={size != null ? { ...style, fontSize: size } : style}
- className={classNames(
- colorName && `cru-${colorName}`,
- `bi-${icon} cru-icon`,
- className,
- )}
- {...otherProps}
- />
- );
-}
diff --git a/FrontEnd/src/views/common/ImageCropper.css b/FrontEnd/src/views/common/ImageCropper.css
deleted file mode 100644
index 2c4d0a8c..00000000
--- a/FrontEnd/src/views/common/ImageCropper.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.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
deleted file mode 100644
index fcab74b0..00000000
--- a/FrontEnd/src/views/common/ImageCropper.tsx
+++ /dev/null
@@ -1,312 +0,0 @@
-import * as React from "react";
-import classnames from "classnames";
-
-import { UiLogicError } from "@/common";
-
-import "./ImageCropper.css";
-import BlobImage from "./BlobImage";
-
-export interface Clip {
- left: number;
- top: number;
- width: number;
-}
-
-interface NormailizedClip extends Clip {
- height: number;
-}
-
-interface ImageInfo {
- width: number;
- height: number;
- landscape: boolean;
- ratio: number;
- maxClipWidth: number;
- maxClipHeight: number;
-}
-
-interface ImageCropperSavedState {
- clip: NormailizedClip;
- x: number;
- y: number;
- pointerId: number;
-}
-
-export interface ImageCropperProps {
- clip: Clip | null;
- image: string | Blob;
- onChange: (clip: Clip) => void;
- imageElementCallback?: (element: HTMLImageElement | null) => void;
- className?: string;
-}
-
-const ImageCropper = (props: ImageCropperProps): React.ReactElement => {
- const { clip, image, onChange, imageElementCallback, className } = props;
-
- const [oldState, setOldState] = React.useState<ImageCropperSavedState | null>(
- null,
- );
- const [imageInfo, setImageInfo] = React.useState<ImageInfo | null>(null);
-
- const normalizeClip = (c: Clip | null | undefined): NormailizedClip => {
- if (c == null) {
- return { left: 0, top: 0, width: 0, height: 0 };
- }
-
- return {
- left: c.left || 0,
- top: c.top || 0,
- width: c.width || 0,
- height: imageInfo != null ? (c.width || 0) / imageInfo.ratio : 0,
- };
- };
-
- const c = normalizeClip(clip);
-
- const imgElementRef = React.useRef<HTMLImageElement | null>(null);
-
- const onImageRef = React.useCallback(
- (e: HTMLImageElement | null) => {
- imgElementRef.current = e;
- if (imageElementCallback != null && e == null) {
- imageElementCallback(null);
- }
- },
- [imageElementCallback],
- );
-
- const onImageLoad = React.useCallback(
- (e: React.SyntheticEvent<HTMLImageElement>) => {
- const img = e.currentTarget;
- const landscape = img.naturalWidth >= img.naturalHeight;
-
- const info = {
- width: img.naturalWidth,
- height: img.naturalHeight,
- landscape,
- ratio: img.naturalHeight / img.naturalWidth,
- maxClipWidth: landscape ? img.naturalHeight / img.naturalWidth : 1,
- maxClipHeight: landscape ? 1 : img.naturalWidth / img.naturalHeight,
- };
- setImageInfo(info);
- onChange({ left: 0, top: 0, width: info.maxClipWidth });
- if (imageElementCallback != null) {
- imageElementCallback(img);
- }
- },
- [onChange, imageElementCallback],
- );
-
- const onPointerDown = React.useCallback(
- (e: React.PointerEvent) => {
- if (oldState != null) return;
- e.currentTarget.setPointerCapture(e.pointerId);
- setOldState({
- x: e.clientX,
- y: e.clientY,
- clip: c,
- pointerId: e.pointerId,
- });
- },
- [oldState, c],
- );
-
- const onPointerUp = React.useCallback(
- (e: React.PointerEvent) => {
- if (oldState == null || oldState.pointerId !== e.pointerId) return;
- e.currentTarget.releasePointerCapture(e.pointerId);
- setOldState(null);
- },
- [oldState],
- );
-
- const onPointerMove = React.useCallback(
- (e: React.PointerEvent) => {
- if (oldState == null) return;
-
- const oldClip = oldState.clip;
-
- const movement = { x: e.clientX - oldState.x, y: e.clientY - oldState.y };
-
- const { current: imgElement } = imgElementRef;
-
- if (imgElement == null) throw new UiLogicError("Image element is null.");
-
- const moveRatio = {
- x: movement.x / imgElement.width,
- y: movement.y / imgElement.height,
- };
-
- const newRatio = {
- x: oldClip.left + moveRatio.x,
- y: oldClip.top + moveRatio.y,
- };
- if (newRatio.x < 0) {
- newRatio.x = 0;
- } else if (newRatio.x > 1 - oldClip.width) {
- newRatio.x = 1 - oldClip.width;
- }
- if (newRatio.y < 0) {
- newRatio.y = 0;
- } else if (newRatio.y > 1 - oldClip.height) {
- newRatio.y = 1 - oldClip.height;
- }
-
- onChange({ left: newRatio.x, top: newRatio.y, width: oldClip.width });
- },
- [oldState, onChange],
- );
-
- const onHandlerPointerMove = React.useCallback(
- (e: React.PointerEvent) => {
- if (oldState == null) return;
-
- const oldClip = oldState.clip;
-
- const movement = { x: e.clientX - oldState.x, y: e.clientY - oldState.y };
-
- const ratio = imageInfo == null ? 1 : imageInfo.ratio;
-
- const { current: imgElement } = imgElementRef;
-
- if (imgElement == null) throw new UiLogicError("Image element is null.");
-
- const moveRatio = {
- x: movement.x / imgElement.width,
- y: movement.x / imgElement.width / ratio,
- };
-
- const newRatio = {
- x: oldClip.width + moveRatio.x,
- y: oldClip.height + moveRatio.y,
- };
-
- const maxRatio = {
- x: Math.min(1 - oldClip.left, newRatio.x),
- y: Math.min(1 - oldClip.top, newRatio.y),
- };
-
- const maxWidthRatio = Math.min(maxRatio.x, maxRatio.y * ratio);
-
- let newWidth;
- if (newRatio.x < 0) {
- newWidth = 0;
- } else if (newRatio.x > maxWidthRatio) {
- newWidth = maxWidthRatio;
- } else {
- newWidth = newRatio.x;
- }
-
- onChange({ left: oldClip.left, top: oldClip.top, width: newWidth });
- },
- [imageInfo, oldState, onChange],
- );
-
- const toPercentage = (n: number): string => `${n}%`;
-
- // fuck!!! I just can't find a better way to implement this in pure css
- const containerStyle: React.CSSProperties = (() => {
- if (imageInfo == null) {
- return { width: "100%", paddingTop: "100%", height: 0 };
- } else {
- if (imageInfo.ratio > 1) {
- return {
- width: toPercentage(100 / imageInfo.ratio),
- paddingTop: "100%",
- height: 0,
- };
- } else {
- return {
- width: "100%",
- paddingTop: toPercentage(100 * imageInfo.ratio),
- height: 0,
- };
- }
- }
- })();
-
- return (
- <div
- className={classnames("image-cropper-container", className)}
- style={containerStyle}
- >
- <BlobImage
- imgRef={onImageRef}
- src={image}
- onLoad={onImageLoad}
- alt="to crop"
- />
- <div className="image-cropper-mask-container">
- <div
- className="image-cropper-mask"
- style={{
- left: toPercentage(c.left * 100),
- top: toPercentage(c.top * 100),
- width: toPercentage(c.width * 100),
- height: toPercentage(c.height * 100),
- }}
- onPointerMove={onPointerMove}
- onPointerDown={onPointerDown}
- onPointerUp={onPointerUp}
- />
- </div>
- <div
- className="image-cropper-handler"
- style={{
- left: `calc(${(c.left + c.width) * 100}% - 15px)`,
- top: `calc(${(c.top + c.height) * 100}% - 15px)`,
- }}
- onPointerMove={onHandlerPointerMove}
- onPointerDown={onPointerDown}
- onPointerUp={onPointerUp}
- />
- </div>
- );
-};
-
-export default ImageCropper;
-
-export function applyClipToImage(
- image: HTMLImageElement,
- clip: Clip,
- mimeType: string,
-): Promise<Blob> {
- return new Promise((resolve, reject) => {
- const naturalSize = {
- width: image.naturalWidth,
- height: image.naturalHeight,
- };
- const clipArea = {
- x: naturalSize.width * clip.left,
- y: naturalSize.height * clip.top,
- length: naturalSize.width * clip.width,
- };
-
- const canvas = document.createElement("canvas");
- canvas.width = clipArea.length;
- canvas.height = clipArea.length;
- const context = canvas.getContext("2d");
-
- if (context == null) throw new Error("Failed to create context.");
-
- context.drawImage(
- image,
- clipArea.x,
- clipArea.y,
- clipArea.length,
- clipArea.length,
- 0,
- 0,
- clipArea.length,
- clipArea.length,
- );
-
- canvas.toBlob((blob) => {
- if (blob == null) {
- reject(new Error("canvas.toBlob returns null"));
- } else {
- resolve(blob);
- }
- }, mimeType);
- });
-}
diff --git a/FrontEnd/src/views/common/LoadFailReload.tsx b/FrontEnd/src/views/common/LoadFailReload.tsx
deleted file mode 100644
index 81ba1f67..00000000
--- a/FrontEnd/src/views/common/LoadFailReload.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import * as React from "react";
-import { Trans } from "react-i18next";
-
-export interface LoadFailReloadProps {
- className?: string;
- style?: React.CSSProperties;
- onReload: () => void;
-}
-
-const LoadFailReload: React.FC<LoadFailReloadProps> = ({
- onReload,
- className,
- style,
-}) => {
- return (
- <Trans
- i18nKey="loadFailReload"
- parent="div"
- className={className}
- style={style}
- >
- 0
- <a
- href="#"
- onClick={(e) => {
- onReload();
- e.preventDefault();
- }}
- >
- 1
- </a>
- 2
- </Trans>
- );
-};
-
-export default LoadFailReload;
diff --git a/FrontEnd/src/views/common/LoadingPage.tsx b/FrontEnd/src/views/common/LoadingPage.tsx
deleted file mode 100644
index 35ee1aa8..00000000
--- a/FrontEnd/src/views/common/LoadingPage.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as React from "react";
-
-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 />
- </div>
- );
-};
-
-export default LoadingPage;
diff --git a/FrontEnd/src/views/common/Page.tsx b/FrontEnd/src/views/common/Page.tsx
deleted file mode 100644
index 86fdb2f5..00000000
--- a/FrontEnd/src/views/common/Page.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ComponentPropsWithoutRef, Ref } from "react";
-import classNames from "classnames";
-
-interface PageProps extends ComponentPropsWithoutRef<"div"> {
- noTopPadding?: boolean;
- pageRef?: Ref<HTMLDivElement>;
-}
-
-export default function Page({ noTopPadding, pageRef, className, children }: PageProps) {
- return (
- <div ref={pageRef} className={classNames(className, "cru-page", noTopPadding && "cru-page-no-top-padding")}>
- {children}
- </div>
- );
-}
diff --git a/FrontEnd/src/views/common/SearchInput.css b/FrontEnd/src/views/common/SearchInput.css
deleted file mode 100644
index f0503016..00000000
--- a/FrontEnd/src/views/common/SearchInput.css
+++ /dev/null
@@ -1,8 +0,0 @@
-.cru-search-input {
- display: flex;
- flex-wrap: wrap;
-}
-
-.cru-search-input-input {
- width: 100%;
-}
diff --git a/FrontEnd/src/views/common/SearchInput.tsx b/FrontEnd/src/views/common/SearchInput.tsx
deleted file mode 100644
index e3216b86..00000000
--- a/FrontEnd/src/views/common/SearchInput.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import classNames from "classnames";
-
-import { useC, Text } from "./common";
-import LoadingButton from "./button/LoadingButton";
-
-import "./SearchInput.css";
-
-interface SearchInputProps {
- value: string;
- onChange: (value: string) => void;
- onButtonClick: () => void;
- loading?: boolean;
- className?: string;
- buttonText?: Text;
-}
-
-export default function SearchInput({
- value,
- onChange,
- onButtonClick,
- loading,
- className,
- buttonText,
-}: SearchInputProps) {
- const c = useC();
-
- return (
- <div className={classNames("cru-search-input", className)}>
- <input
- type="text"
- className="cru-search-input-input"
- value={value}
- onChange={(event) => {
- const { value } = event.currentTarget;
- onChange(value);
- }}
- onKeyDown={(event) => {
- if (event.key === "Enter") {
- onButtonClick();
- event.preventDefault();
- }
- }}
- />
-
- <LoadingButton loading={loading} onClick={onButtonClick}>
- {c(buttonText ?? "search")}
- </LoadingButton>
- </div>
- );
-}
diff --git a/FrontEnd/src/views/common/Skeleton.css b/FrontEnd/src/views/common/Skeleton.css
deleted file mode 100644
index a571eead..00000000
--- a/FrontEnd/src/views/common/Skeleton.css
+++ /dev/null
@@ -1,14 +0,0 @@
-.cru-skeleton {
- padding: 0 1em;
-}
-
-.cru-skeleton-line {
- height: 1em;
- background-color: hsl(0, 0%, 90%);
- margin: 0.7em 0;
- border-radius: 0.2em;
-}
-
-.cru-skeleton-line.last {
- width: 50%;
-}
diff --git a/FrontEnd/src/views/common/Skeleton.tsx b/FrontEnd/src/views/common/Skeleton.tsx
deleted file mode 100644
index 3b149db9..00000000
--- a/FrontEnd/src/views/common/Skeleton.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import * as React from "react";
-import classnames from "classnames";
-import range from "lodash/range";
-
-import "./Skeleton.css";
-
-export interface SkeletonProps {
- lineNumber?: number;
- className?: string;
- style?: React.CSSProperties;
-}
-
-const Skeleton: React.FC<SkeletonProps> = (props) => {
- const { lineNumber: lineNumberProps, className, style } = props;
- const lineNumber = lineNumberProps ?? 3;
-
- return (
- <div className={classnames(className, "cru-skeleton")} style={style}>
- {range(lineNumber).map((i) => (
- <div
- key={i}
- className={classnames(
- "cru-skeleton-line",
- i === lineNumber - 1 && "last"
- )}
- />
- ))}
- </div>
- );
-};
-
-export default Skeleton;
diff --git a/FrontEnd/src/views/common/Spinner.css b/FrontEnd/src/views/common/Spinner.css
deleted file mode 100644
index a1de68d2..00000000
--- a/FrontEnd/src/views/common/Spinner.css
+++ /dev/null
@@ -1,13 +0,0 @@
-@keyframes cru-spinner-animation {
- from {
- transform: scale(0,0);
- }
-}
-
-.cru-spinner {
- display: inline-block;
- animation: cru-spinner-animation 0.5s infinite alternate;
- background-color: currentColor;
- border-radius: 50%;
- transform-origin: center;
-}
diff --git a/FrontEnd/src/views/common/Spinner.tsx b/FrontEnd/src/views/common/Spinner.tsx
deleted file mode 100644
index ec0c2c35..00000000
--- a/FrontEnd/src/views/common/Spinner.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { CSSProperties } from "react";
-import classnames from "classnames";
-
-import { ThemeColor } from "./common";
-
-import "./Spinner.css";
-
-export interface SpinnerProps {
- size?: "sm" | "md" | "lg" | number | string;
- color?: ThemeColor;
- className?: string;
- style?: CSSProperties;
-}
-
-export default function Spinner(props: SpinnerProps) {
- const { size, color, className, style } = props;
- const calculatedSize =
- size === "sm"
- ? "18px"
- : size === "md"
- ? "30px"
- : size === "lg"
- ? "42px"
- : typeof size === "number"
- ? size
- : size == null
- ? "20px"
- : size;
-
- return (
- <span
- className={classnames("cru-spinner", color && `cru-${color}`, className)}
- style={{ width: calculatedSize, height: calculatedSize, ...style }}
- />
- );
-}
diff --git a/FrontEnd/src/views/common/TimelineLogo.tsx b/FrontEnd/src/views/common/TimelineLogo.tsx
deleted file mode 100644
index e06ed0f5..00000000
--- a/FrontEnd/src/views/common/TimelineLogo.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { SVGAttributes } from "react";
-import * as React from "react";
-
-export interface TimelineLogoProps extends SVGAttributes<SVGElement> {
- color?: string;
-}
-
-const TimelineLogo: React.FC<TimelineLogoProps> = (props) => {
- const { color, ...forwardProps } = props;
- const coercedColor = color ?? "currentcolor";
- return (
- <svg
- className={props.className}
- viewBox="0 0 100 100"
- fill="none"
- strokeWidth="12"
- stroke={coercedColor}
- {...forwardProps}
- >
- <line x1="50" y1="0" x2="50" y2="25" />
- <circle cx="50" cy="50" r="22" />
- <line x1="50" y1="75" x2="50" y2="100" />
- </svg>
- );
-};
-
-export default TimelineLogo;
diff --git a/FrontEnd/src/views/common/alert/AlertHost.tsx b/FrontEnd/src/views/common/alert/AlertHost.tsx
deleted file mode 100644
index 42074781..00000000
--- a/FrontEnd/src/views/common/alert/AlertHost.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import * as React from "react";
-import without from "lodash/without";
-import { useTranslation } from "react-i18next";
-import classNames from "classnames";
-
-import { alertService, AlertInfoEx, AlertInfo } from "@/services/alert";
-import { convertI18nText } from "@/common";
-
-import IconButton from "../button/IconButton";
-
-import "./alert.css";
-
-interface AutoCloseAlertProps {
- alert: AlertInfo;
- close: () => void;
-}
-
-export const AutoCloseAlert: React.FC<AutoCloseAlertProps> = (props) => {
- const { alert, close } = props;
- const { dismissTime } = alert;
-
- const { t } = useTranslation();
-
- const timerTag = React.useRef<number | null>(null);
- const closeHandler = React.useRef<(() => void) | null>(null);
-
- React.useEffect(() => {
- closeHandler.current = close;
- }, [close]);
-
- React.useEffect(() => {
- const tag =
- dismissTime === "never"
- ? null
- : typeof dismissTime === "number"
- ? window.setTimeout(() => closeHandler.current?.(), dismissTime)
- : window.setTimeout(() => closeHandler.current?.(), 5000);
- timerTag.current = tag;
- return () => {
- if (tag != null) {
- window.clearTimeout(tag);
- }
- };
- }, [dismissTime]);
-
- const cancelTimer = (): void => {
- const { current: tag } = timerTag;
- if (tag != null) {
- window.clearTimeout(tag);
- }
- };
-
- return (
- <div
- className={classNames(
- "m-3 cru-alert",
- "cru-" + (alert.type ?? "primary")
- )}
- onClick={cancelTimer}
- >
- <div className="cru-alert-content">
- {(() => {
- const { message, customMessage } = alert;
- if (customMessage != null) {
- return customMessage;
- } else {
- return convertI18nText(message, t);
- }
- })()}
- </div>
- <div className="cru-alert-close-button-container">
- <IconButton
- icon="x"
- className="cru-alert-close-button"
- onClick={close}
- />
- </div>
- </div>
- );
-};
-
-const AlertHost: React.FC = () => {
- const [alerts, setAlerts] = React.useState<AlertInfoEx[]>([]);
-
- React.useEffect(() => {
- const consume = (alert: AlertInfoEx): void => {
- setAlerts((old) => [...old, alert]);
- };
-
- alertService.registerConsumer(consume);
- return () => {
- alertService.unregisterConsumer(consume);
- };
- }, []);
-
- return (
- <div className="alert-container">
- {alerts.map((alert) => {
- return (
- <AutoCloseAlert
- key={alert.id}
- alert={alert}
- close={() => {
- setAlerts((old) => without(old, alert));
- }}
- />
- );
- })}
- </div>
- );
-};
-
-export default AlertHost;
diff --git a/FrontEnd/src/views/common/alert/alert.css b/FrontEnd/src/views/common/alert/alert.css
deleted file mode 100644
index 54c2b87f..00000000
--- a/FrontEnd/src/views/common/alert/alert.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.alert-container {
- position: fixed;
- z-index: 1040;
-}
-
-.cru-alert {
- border-radius: 5px;
- border: var(--cru-key-color) 1px solid;
- color: var(--cru-key-t-color);
- background-color: var(--cru-key-b1-color);
-
- display: flex;
- overflow: hidden;
-}
-
-.cru-alert-content {
- padding: 0.5em 2em;
-}
-
-.cru-alert-close-button-container {
- flex-shrink: 0;
- margin-left: auto;
- width: 2em;
- text-align: center;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: var(--cru-key-t-color);
-}
-
-.cru-alert-close-button {
- color: var(--cru-key-color);
-}
diff --git a/FrontEnd/src/views/common/breakpoints.ts b/FrontEnd/src/views/common/breakpoints.ts
deleted file mode 100644
index fb281610..00000000
--- a/FrontEnd/src/views/common/breakpoints.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const breakpoints = {
- sm: 576,
-} as const;
diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css
deleted file mode 100644
index 1da70f0e..00000000
--- a/FrontEnd/src/views/common/button/Button.css
+++ /dev/null
@@ -1,64 +0,0 @@
-.cru-button {
- font-size: 1rem;
- padding: 0.4em 0.8em;
- transition: all 0.3s;
- border-radius: 0.2em;
- border: 1px solid;
- cursor: pointer;
-}
-
-.cru-button:not(.outline) {
- color: var(--cru-push-button-text-color);
- background-color: var(--cru-clickable-normal-color);
- border-color: var(--cru-clickable-normal-color);
-}
-
-.cru-button:not(.outline):hover {
- background-color: var(--cru-clickable-hover-color);
- border-color: var(--cru-clickable-hover-color);
-}
-
-.cru-button:not(.outline):focus {
- background-color: var(--cru-clickable-focus-color);
- border-color: var(--cru-clickable-focus-color);
-}
-
-.cru-button:not(.outline):active {
- background-color: var(--cru-clickable-active-color);
- border-color: var(--cru-clickable-active-color);
-}
-
-.cru-button:not(.outline):disabled {
- color: var(--cru-push-button-disabled-text-color);
- background-color: var(--cru-push-button-disabled-color);
- border-color: var(--cru-push-button-disabled-color);
- cursor: auto;
-}
-
-
-.cru-button.outline {
- color: var(--cru-clickable-normal-color);
- border-color: var(--cru-clickable-normal-color);
- background-color: transparent;
-}
-
-.cru-button.outline:hover {
- color: var(--cru-clickable-hover-color);
- border-color: var(--cru-clickable-hover-color);
-}
-
-.cru-button.outline:focus {
- color: var(--cru-clickable-focus-color);
- border-color: var(--cru-clickable-focus-color);
-}
-
-.cru-button.outline:active {
- color: var(--cru-clickable-active-color);
- border-color: var(--cru-clickable-active-color);
-}
-
-.cru-button.outline:disabled {
- color: var(--cru-clickable-disabled-color);
- border-color: var(--cru-clickable-disabled-color);
- cursor: auto;
-}
diff --git a/FrontEnd/src/views/common/button/Button.tsx b/FrontEnd/src/views/common/button/Button.tsx
deleted file mode 100644
index 6c38e130..00000000
--- a/FrontEnd/src/views/common/button/Button.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { ComponentPropsWithoutRef, Ref } from "react";
-import classNames from "classnames";
-
-import { Text, useC, ThemeColor } from "../common";
-
-import "./Button.css";
-
-interface ButtonProps extends ComponentPropsWithoutRef<"button"> {
- color?: ThemeColor;
- text?: Text;
- outline?: boolean;
- buttonRef?: Ref<HTMLButtonElement> | null;
-}
-
-export default function Button(props: ButtonProps) {
- const {
- buttonRef,
- color,
- text,
- outline,
- className,
- children,
- ...otherProps
- } = props;
-
- if (text != null && children != null) {
- console.warn("You can't set both text and children props.");
- }
-
- const c = useC();
-
- return (
- <button
- ref={buttonRef}
- className={classNames(
- "cru-button",
- `cru-clickable-${color ?? "primary"}`,
- outline && "outline",
- className,
- )}
- {...otherProps}
- >
- {text != null ? c(text) : children}
- </button>
- );
-}
diff --git a/FrontEnd/src/views/common/button/ButtonRow.css b/FrontEnd/src/views/common/button/ButtonRow.css
deleted file mode 100644
index e69de29b..00000000
--- a/FrontEnd/src/views/common/button/ButtonRow.css
+++ /dev/null
diff --git a/FrontEnd/src/views/common/button/ButtonRow.tsx b/FrontEnd/src/views/common/button/ButtonRow.tsx
deleted file mode 100644
index eea60cc4..00000000
--- a/FrontEnd/src/views/common/button/ButtonRow.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { ComponentPropsWithoutRef, Ref } from "react";
-import classNames from "classnames";
-
-import Button from "./Button";
-import FlatButton from "./FlatButton";
-import IconButton from "./IconButton";
-import LoadingButton from "./LoadingButton";
-
-import "./ButtonRow.css";
-
-type ButtonRowButton = (
- | {
- type: "normal";
- props: ComponentPropsWithoutRef<typeof Button>;
- }
- | {
- type: "flat";
- props: ComponentPropsWithoutRef<typeof FlatButton>;
- }
- | {
- type: "icon";
- props: ComponentPropsWithoutRef<typeof IconButton>;
- }
- | { type: "loading"; props: ComponentPropsWithoutRef<typeof LoadingButton> }
-) & { key: string | number };
-
-interface ButtonRowProps {
- className?: string;
- containerRef?: Ref<HTMLDivElement>;
- buttons: ButtonRowButton[];
- buttonsClassName?: string;
-}
-
-export default function ButtonRow({
- className,
- containerRef,
- buttons,
- buttonsClassName,
-}: ButtonRowProps) {
- return (
- <div ref={containerRef} className={classNames("cru-button-row", className)}>
- {buttons.map((button) => {
- const { type, key, props } = button;
- const newClassName = classNames(props.className, buttonsClassName);
- switch (type) {
- case "normal":
- return <Button key={key} {...props} className={newClassName} />;
- case "flat":
- return <FlatButton key={key} {...props} className={newClassName} />;
- case "icon":
- return <IconButton key={key} {...props} className={newClassName} />;
- case "loading":
- return (
- <LoadingButton key={key} {...props} className={newClassName} />
- );
- default:
- throw new Error();
- }
- })}
- </div>
- );
-}
diff --git a/FrontEnd/src/views/common/button/ButtonRowV2.tsx b/FrontEnd/src/views/common/button/ButtonRowV2.tsx
deleted file mode 100644
index 3467ad52..00000000
--- a/FrontEnd/src/views/common/button/ButtonRowV2.tsx
+++ /dev/null
@@ -1,143 +0,0 @@
-import { ComponentPropsWithoutRef, Ref } from "react";
-import classNames from "classnames";
-
-import Button from "./Button";
-import FlatButton from "./FlatButton";
-import IconButton from "./IconButton";
-import LoadingButton from "./LoadingButton";
-
-import "./ButtonRow.css";
-import { Text, ThemeColor } from "../common";
-
-interface ButtonRowV2ButtonBase {
- key: string | number;
- action?: "primary" | "secondary";
- color?: ThemeColor;
- disabled?: boolean;
- onClick?: () => void;
-}
-
-interface ButtonRowV2ButtonWithNoType extends ButtonRowV2ButtonBase {
- type?: undefined | null;
- text: Text;
- outline?: boolean;
- props?: ComponentPropsWithoutRef<typeof Button>;
-}
-
-interface ButtonRowV2NormalButton extends ButtonRowV2ButtonBase {
- type: "normal";
- text: Text;
- outline?: boolean;
- props?: ComponentPropsWithoutRef<typeof Button>;
-}
-
-interface ButtonRowV2FlatButton extends ButtonRowV2ButtonBase {
- type: "flat";
- text: Text;
- props?: ComponentPropsWithoutRef<typeof FlatButton>;
-}
-
-interface ButtonRowV2IconButton extends ButtonRowV2ButtonBase {
- type: "icon";
- icon: string;
- props?: ComponentPropsWithoutRef<typeof IconButton>;
-}
-
-interface ButtonRowV2LoadingButton extends ButtonRowV2ButtonBase {
- type: "loading";
- text: Text;
- loading?: boolean;
- props?: ComponentPropsWithoutRef<typeof LoadingButton>;
-}
-
-type ButtonRowV2Button =
- | ButtonRowV2ButtonWithNoType
- | ButtonRowV2NormalButton
- | ButtonRowV2FlatButton
- | ButtonRowV2IconButton
- | ButtonRowV2LoadingButton;
-
-interface ButtonRowV2Props {
- className?: string;
- containerRef?: Ref<HTMLDivElement>;
- buttons: ButtonRowV2Button[];
- buttonsClassName?: string;
-}
-
-export default function ButtonRowV2({
- className,
- containerRef,
- buttons,
- buttonsClassName,
-}: ButtonRowV2Props) {
- return (
- <div ref={containerRef} className={classNames("cru-button-row", className)}>
- {buttons.map((button) => {
- const { key, action, color, disabled, onClick } = button;
-
- const realAction = action ?? "primary";
- const realColor =
- color ?? (realAction === "primary" ? "primary" : "secondary");
-
- const commonProps = { key, color: realColor, disabled, onClick };
- const newClassName = classNames(
- button.props?.className,
- buttonsClassName,
- );
-
- switch (button.type) {
- case null:
- case undefined:
- case "normal": {
- const { text, outline, props } = button;
- return (
- <Button
- {...commonProps}
- text={text}
- outline={outline ?? realAction !== "primary"}
- {...props}
- className={newClassName}
- />
- );
- }
- case "flat": {
- const { text, props } = button;
- return (
- <FlatButton
- {...commonProps}
- text={text}
- {...props}
- className={newClassName}
- />
- );
- }
- case "icon": {
- const { icon, props } = button;
- return (
- <IconButton
- {...commonProps}
- icon={icon}
- {...props}
- className={newClassName}
- />
- );
- }
- case "loading": {
- const { text, loading, props } = button;
- return (
- <LoadingButton
- {...commonProps}
- text={text}
- loading={loading}
- {...props}
- className={newClassName}
- />
- );
- }
- default:
- throw new Error();
- }
- })}
- </div>
- );
-}
diff --git a/FrontEnd/src/views/common/button/FlatButton.css b/FrontEnd/src/views/common/button/FlatButton.css
deleted file mode 100644
index 2050946c..00000000
--- a/FrontEnd/src/views/common/button/FlatButton.css
+++ /dev/null
@@ -1,27 +0,0 @@
-.cru-flat-button {
- font-size: 1rem;
- padding: 0.4em 0.8em;
- transition: all 0.5s;
- border-radius: 0.2em;
- background-color: var(--cru-clickable-grayscale-normal-color);
- border: 1px none;
- color: var(--cru-clickable-normal-color);
- cursor: pointer;
-}
-
-.cru-flat-button:hover {
- background-color: var(--cru-clickable-grayscale-hover-color);
-}
-
-.cru-flat-button:focus {
- background-color: var(--cru-clickable-grayscale-focus-color);
-}
-
-.cru-flat-button:active {
- background-color: var(--cru-clickable-grayscale-active-color);
-}
-
-.cru-flat-button:disabled {
- color: var(--cru-clickable-disabled-color);
- cursor: auto;
-} \ No newline at end of file
diff --git a/FrontEnd/src/views/common/button/FlatButton.tsx b/FrontEnd/src/views/common/button/FlatButton.tsx
deleted file mode 100644
index 9f074dd6..00000000
--- a/FrontEnd/src/views/common/button/FlatButton.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { ComponentPropsWithoutRef, Ref } from "react";
-import classNames from "classnames";
-
-import { Text, useC, ThemeColor } from "../common";
-
-import "./FlatButton.css";
-
-interface FlatButtonProps extends ComponentPropsWithoutRef<"button"> {
- color?: ThemeColor;
- text?: Text;
- buttonRef?: Ref<HTMLButtonElement> | null;
-}
-
-export default function FlatButton(props: FlatButtonProps) {
- const { color, text, className, children, buttonRef, ...otherProps } = props;
-
- if (text != null && children != null) {
- console.warn("You can't set both text and children props.");
- }
-
- const c = useC();
-
- return (
- <button
- ref={buttonRef}
- className={classNames(
- "cru-flat-button",
- `cru-clickable-${color ?? "primary"}`,
- className,
- )}
- {...otherProps}
- >
- {text != null ? c(text) : children}
- </button>
- );
-}
diff --git a/FrontEnd/src/views/common/button/IconButton.css b/FrontEnd/src/views/common/button/IconButton.css
deleted file mode 100644
index a3747201..00000000
--- a/FrontEnd/src/views/common/button/IconButton.css
+++ /dev/null
@@ -1,30 +0,0 @@
-.cru-icon-button {
- color: var(--cru-clickable-normal-color);
- font-size: 1.4rem;
- background: none;
- border: none;
- transition: all 0.5s;
- cursor: pointer;
- user-select: none;
-}
-
-.cru-icon-button:hover {
- color: var(--cru-clickable-hover-color);
-}
-
-.cru-icon-button:focus {
- color: var(--cru-clickable-focus-color);
-}
-
-.cru-icon-button:active {
- color: var(--cru-clickable-active-color);
-}
-
-.cru-flat-button:disabled {
- color: var(--cru-clickable-disabled-color);
- cursor: auto;
-}
-
-.cru-icon-button.large {
- font-size: 1.6rem;
-}
diff --git a/FrontEnd/src/views/common/button/IconButton.tsx b/FrontEnd/src/views/common/button/IconButton.tsx
deleted file mode 100644
index 95c58887..00000000
--- a/FrontEnd/src/views/common/button/IconButton.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { ComponentPropsWithoutRef } from "react";
-import classNames from "classnames";
-
-import { ThemeColor } from "../common";
-
-import "./IconButton.css";
-
-interface IconButtonProps extends ComponentPropsWithoutRef<"i"> {
- icon: string;
- color?: ThemeColor | "grayscale";
- large?: boolean;
- disabled?: boolean; // TODO: Not implemented
-}
-
-export default function IconButton(props: IconButtonProps) {
- const { icon, color, className, large, ...otherProps } = props;
-
- return (
- <button
- className={classNames(
- "cru-icon-button",
- `cru-clickable-${color ?? "grayscale"}`,
- large && "large",
- "bi-" + icon,
- className,
- )}
- {...otherProps}
- />
- );
-}
diff --git a/FrontEnd/src/views/common/button/LoadingButton.css b/FrontEnd/src/views/common/button/LoadingButton.css
deleted file mode 100644
index 23fadd3d..00000000
--- a/FrontEnd/src/views/common/button/LoadingButton.css
+++ /dev/null
@@ -1,13 +0,0 @@
-.cru-loading-button {
- display: flex;
- align-items: center;
-}
-
-.cru-loading-button-spinner {
- margin-left: 0.5em;
-}
-
-.cru-loading-button-loading {
- color: var(--cru-clickable-normal-color) !important;
- border-color: var(--cru-clickable-normal-color) !important;
-} \ No newline at end of file
diff --git a/FrontEnd/src/views/common/button/LoadingButton.tsx b/FrontEnd/src/views/common/button/LoadingButton.tsx
deleted file mode 100644
index 7e7d08e6..00000000
--- a/FrontEnd/src/views/common/button/LoadingButton.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import classNames from "classnames";
-
-import { I18nText, ThemeColor, useC } from "../common";
-
-import Spinner from "../Spinner";
-
-import "./LoadingButton.css";
-
-interface LoadingButtonProps extends React.ComponentPropsWithoutRef<"button"> {
- color?: ThemeColor;
- text?: I18nText;
- loading?: boolean;
-}
-
-export default function LoadingButton(props: LoadingButtonProps) {
- const c = useC();
-
- const { color, text, loading, disabled, className, children, ...otherProps } =
- props;
-
- if (text != null && children != null) {
- console.warn("You can't set both text and children props.");
- }
-
- return (
- <button
- disabled={disabled || loading}
- className={classNames(
- "cru-button outline cru-loading-button",
- `cru-clickable-${color ?? "primary"}`,
- loading && "cru-loading-button-loading",
- className,
- )}
- {...otherProps}
- >
- {text != null ? c(text) : children}
- {loading && <Spinner className="cru-loading-button-spinner" />}
- </button>
- );
-}
diff --git a/FrontEnd/src/views/common/button/index.tsx b/FrontEnd/src/views/common/button/index.tsx
deleted file mode 100644
index b5aa5470..00000000
--- a/FrontEnd/src/views/common/button/index.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import Button from "./Button";
-import FlatButton from "./FlatButton";
-import IconButton from "./IconButton";
-import LoadingButton from "./LoadingButton";
-import ButtonRow from "./ButtonRow";
-import ButtonRowV2 from "./ButtonRowV2";
-
-export {
- Button,
- FlatButton,
- IconButton,
- LoadingButton,
- ButtonRow,
- ButtonRowV2,
-};
diff --git a/FrontEnd/src/views/common/common.ts b/FrontEnd/src/views/common/common.ts
deleted file mode 100644
index 7af2643b..00000000
--- a/FrontEnd/src/views/common/common.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-export type { Text, I18nText } from "@/common";
-export { c, convertI18nText, useC } from "@/common";
-
-export const themeColors = [
- "primary",
- "secondary",
- "danger",
- "create",
-] as const;
-
-export type ThemeColor = (typeof themeColors)[number];
-
-export { breakpoints } from "./breakpoints";
-export { useMobile } from "./hooks";
diff --git a/FrontEnd/src/views/common/dialog/ConfirmDialog.css b/FrontEnd/src/views/common/dialog/ConfirmDialog.css
deleted file mode 100644
index e69de29b..00000000
--- a/FrontEnd/src/views/common/dialog/ConfirmDialog.css
+++ /dev/null
diff --git a/FrontEnd/src/views/common/dialog/ConfirmDialog.tsx b/FrontEnd/src/views/common/dialog/ConfirmDialog.tsx
deleted file mode 100644
index dbbd15c6..00000000
--- a/FrontEnd/src/views/common/dialog/ConfirmDialog.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { useC, Text, ThemeColor } from "@/views/common/common";
-
-import Dialog from "./Dialog";
-import DialogContainer from "./DialogContainer";
-
-export default function ConfirmDialog({
- open,
- onClose,
- onConfirm,
- title,
- body,
- color,
- bodyColor,
-}: {
- open: boolean;
- onClose: () => void;
- onConfirm: () => void;
- title: Text;
- body: Text;
- color?: ThemeColor;
- bodyColor?: ThemeColor;
-}) {
- const c = useC();
-
- return (
- <Dialog onClose={onClose} open={open}>
- <DialogContainer
- title={title}
- titleColor={color ?? "danger"}
- buttons={[
- {
- key: "cancel",
- type: "normal",
- props: {
- text: "operationDialog.cancel",
- color: "secondary",
- outline: true,
- onClick: onClose,
- },
- },
- {
- key: "confirm",
- type: "normal",
- props: {
- text: "operationDialog.confirm",
- color: "danger",
- onClick: () => {
- onConfirm();
- onClose();
- },
- },
- },
- ]}
- >
- <div className={`cru-${bodyColor ?? "primary"}`}>{c(body)}</div>
- </DialogContainer>
- </Dialog>
- );
-}
diff --git a/FrontEnd/src/views/common/dialog/Dialog.css b/FrontEnd/src/views/common/dialog/Dialog.css
deleted file mode 100644
index e4c61440..00000000
--- a/FrontEnd/src/views/common/dialog/Dialog.css
+++ /dev/null
@@ -1,60 +0,0 @@
-.cru-dialog-overlay {
- position: fixed;
- z-index: 1040;
- left: 0;
- top: 0;
- right: 0;
- bottom: 0;
- display: flex;
- align-items: center;
- overflow: auto;
-}
-
-.cru-dialog-background {
- position: absolute;
- z-index: -1;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- background-color: var(--cru-surface-dim-color);
- opacity: 0.8;
-}
-
-.cru-dialog-container {
- max-width: 100%;
- min-width: 30vw;
-
- margin: 2em auto;
-
- border: var(--cru-key-container-color) 1px solid;
- border-radius: 5px;
- padding: 1.5em;
- background-color: var(--cru-surface-color);
-}
-
-@media (min-width: 576px) {
- .cru-dialog-container {
- max-width: 800px;
- }
-}
-
-.cru-dialog-enter .cru-dialog-container {
- transform: scale(0, 0);
- opacity: 0;
- transform-origin: center;
-}
-
-.cru-dialog-enter-active .cru-dialog-container {
- transform: scale(1, 1);
- opacity: 1;
- transition: transform 0.3s, opacity 0.3s;
- transform-origin: center;
-}
-
-.cru-dialog-exit-active .cru-dialog-container {
- transition: transform 0.3s, opacity 0.3s;
- transform: scale(0, 0);
- opacity: 0;
- transform-origin: center;
-} \ No newline at end of file
diff --git a/FrontEnd/src/views/common/dialog/Dialog.tsx b/FrontEnd/src/views/common/dialog/Dialog.tsx
deleted file mode 100644
index 2ff7bea8..00000000
--- a/FrontEnd/src/views/common/dialog/Dialog.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { ReactNode, useRef } from "react";
-import ReactDOM from "react-dom";
-import { CSSTransition } from "react-transition-group";
-import classNames from "classnames";
-
-import { ThemeColor } from "../common";
-
-import "./Dialog.css";
-
-const optionalPortalElement = document.getElementById("portal");
-if (optionalPortalElement == null) {
- throw new Error("Portal element not found");
-}
-const portalElement = optionalPortalElement;
-
-interface DialogProps {
- open: boolean;
- onClose: () => void;
- color?: ThemeColor;
- children?: ReactNode;
- disableCloseOnClickOnOverlay?: boolean;
-}
-
-export default function Dialog({
- open,
- onClose,
- color,
- children,
- disableCloseOnClickOnOverlay,
-}: DialogProps) {
- color = color ?? "primary";
-
- const nodeRef = useRef(null);
-
- return ReactDOM.createPortal(
- <CSSTransition
- nodeRef={nodeRef}
- mountOnEnter
- unmountOnExit
- in={open}
- timeout={300}
- classNames="cru-dialog"
- >
- <div
- ref={nodeRef}
- className={classNames("cru-dialog-overlay", `cru-${color}`)}
- >
- <div
- className="cru-dialog-background"
- onClick={
- disableCloseOnClickOnOverlay
- ? undefined
- : () => {
- onClose();
- }
- }
- />
- <div className="cru-dialog-container">{children}</div>
- </div>
- </CSSTransition>,
- portalElement,
- );
-}
diff --git a/FrontEnd/src/views/common/dialog/DialogContainer.css b/FrontEnd/src/views/common/dialog/DialogContainer.css
deleted file mode 100644
index fbb18e0d..00000000
--- a/FrontEnd/src/views/common/dialog/DialogContainer.css
+++ /dev/null
@@ -1,20 +0,0 @@
-.cru-dialog-container-title {
- font-size: 1.2em;
- font-weight: bold;
- color: var(--cru-key-color);
- margin-bottom: 0.5em;
-}
-
-
-.cru-dialog-container-hr {
- margin: 1em 0;
-}
-
-.cru-dialog-container-button-row {
- display: flex;
- justify-content: flex-end;
-}
-
-.cru-dialog-container-button {
- margin-left: 1em;
-} \ No newline at end of file
diff --git a/FrontEnd/src/views/common/dialog/DialogContainer.tsx b/FrontEnd/src/views/common/dialog/DialogContainer.tsx
deleted file mode 100644
index afee2669..00000000
--- a/FrontEnd/src/views/common/dialog/DialogContainer.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { ComponentProps, Ref, ReactNode } from "react";
-import classNames from "classnames";
-
-import { ThemeColor, Text, useC } from "../common";
-import { ButtonRow, ButtonRowV2 } from "../button";
-
-import "./DialogContainer.css";
-
-interface DialogContainerBaseProps {
- className?: string;
- title: Text;
- titleColor?: ThemeColor;
- titleClassName?: string;
- titleRef?: Ref<HTMLDivElement>;
- bodyContainerClassName?: string;
- bodyContainerRef?: Ref<HTMLDivElement>;
- buttonsClassName?: string;
- buttonsContainerRef?: ComponentProps<typeof ButtonRow>["containerRef"];
- children: ReactNode;
-}
-
-interface DialogContainerWithButtonsProps extends DialogContainerBaseProps {
- buttons: ComponentProps<typeof ButtonRow>["buttons"];
-}
-
-interface DialogContainerWithButtonsV2Props extends DialogContainerBaseProps {
- buttonsV2: ComponentProps<typeof ButtonRowV2>["buttons"];
-}
-
-type DialogContainerProps =
- | DialogContainerWithButtonsProps
- | DialogContainerWithButtonsV2Props;
-
-export default function DialogContainer(props: DialogContainerProps) {
- const {
- className,
- title,
- titleColor,
- titleClassName,
- titleRef,
- bodyContainerClassName,
- bodyContainerRef,
- buttonsClassName,
- buttonsContainerRef,
- children,
- } = props;
-
- const c = useC();
-
- return (
- <div className={classNames(className)}>
- <div
- ref={titleRef}
- className={classNames(
- `cru-dialog-container-title cru-${titleColor ?? "primary"}`,
- titleClassName,
- )}
- >
- {c(title)}
- </div>
- <hr className="cru-dialog-container-hr" />
- <div
- ref={bodyContainerRef}
- className={classNames(
- "cru-dialog-container-body",
- bodyContainerClassName,
- )}
- >
- {children}
- </div>
- <hr className="cru-dialog-container-hr" />
- {"buttons" in props ? (
- <ButtonRow
- containerRef={buttonsContainerRef}
- className={classNames(
- "cru-dialog-container-button-row",
- buttonsClassName,
- )}
- buttons={props.buttons}
- buttonsClassName="cru-dialog-container-button"
- />
- ) : (
- <ButtonRowV2
- containerRef={buttonsContainerRef}
- className={classNames(
- "cru-dialog-container-button-row",
- buttonsClassName,
- )}
- buttons={props.buttonsV2}
- buttonsClassName="cru-dialog-container-button"
- />
- )}
- </div>
- );
-}
diff --git a/FrontEnd/src/views/common/dialog/FullPageDialog.css b/FrontEnd/src/views/common/dialog/FullPageDialog.css
deleted file mode 100644
index 2f1fc636..00000000
--- a/FrontEnd/src/views/common/dialog/FullPageDialog.css
+++ /dev/null
@@ -1,44 +0,0 @@
-.cru-full-page {
- position: fixed;
- z-index: 1030;
- left: 0;
- top: 0;
- right: 0;
- bottom: 0;
- background-color: white;
- padding-top: 56px;
-}
-
-.cru-full-page-top-bar {
- height: 56px;
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- z-index: 1;
- background-color: var(--cru-primary-color);
- display: flex;
- align-items: center;
-}
-
-.cru-full-page-content-container {
- overflow: scroll;
-}
-
-.cru-full-page-back-button {
- color: var(--cru-primary-t-color);
-}
-
-.cru-full-page-enter {
- transform: translate(100%, 0);
-}
-
-.cru-full-page-enter-active {
- transform: none;
- transition: transform 0.3s;
-}
-
-.cru-full-page-exit-active {
- transition: transform 0.3s;
- transform: translate(100%, 0);
-}
diff --git a/FrontEnd/src/views/common/dialog/FullPageDialog.tsx b/FrontEnd/src/views/common/dialog/FullPageDialog.tsx
deleted file mode 100644
index 6368fc0a..00000000
--- a/FrontEnd/src/views/common/dialog/FullPageDialog.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import * as React from "react";
-import { createPortal } from "react-dom";
-import classnames from "classnames";
-import { CSSTransition } from "react-transition-group";
-
-import "./FullPageDialog.css";
-import IconButton from "../button/IconButton";
-
-export interface FullPageDialogProps {
- show: boolean;
- onBack: () => void;
- contentContainerClassName?: string;
- children: React.ReactNode;
-}
-
-const FullPageDialog: React.FC<FullPageDialogProps> = ({
- show,
- onBack,
- children,
- contentContainerClassName,
-}) => {
- return createPortal(
- <CSSTransition
- mountOnEnter
- unmountOnExit
- in={show}
- timeout={300}
- classNames="cru-full-page"
- >
- <div className="cru-full-page">
- <div className="cru-full-page-top-bar">
- <IconButton
- icon="arrow-left"
- className="ms-3 cru-full-page-back-button"
- onClick={onBack}
- />
- </div>
- <div
- className={classnames(
- "cru-full-page-content-container",
- contentContainerClassName
- )}
- >
- {children}
- </div>
- </div>
- </CSSTransition>,
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- document.getElementById("portal")!
- );
-};
-
-export default FullPageDialog;
diff --git a/FrontEnd/src/views/common/dialog/OperationDialog.css b/FrontEnd/src/views/common/dialog/OperationDialog.css
deleted file mode 100644
index f4b7237e..00000000
--- a/FrontEnd/src/views/common/dialog/OperationDialog.css
+++ /dev/null
@@ -1,8 +0,0 @@
-.cru-operation-dialog-prompt {
- color: var(--cru-surface-on-color);
-}
-
-.cru-operation-dialog-input-group {
- display: block;
- margin: 0.5em 0;
-}
diff --git a/FrontEnd/src/views/common/dialog/OperationDialog.tsx b/FrontEnd/src/views/common/dialog/OperationDialog.tsx
deleted file mode 100644
index 4335b2b0..00000000
--- a/FrontEnd/src/views/common/dialog/OperationDialog.tsx
+++ /dev/null
@@ -1,228 +0,0 @@
-import { useState, ReactNode, ComponentProps } from "react";
-import classNames from "classnames";
-
-import { useC, Text, ThemeColor } from "../common";
-
-import {
- useInputs,
- InputGroup,
- Initializer as InputInitializer,
- InputValueDict,
- InputErrorDict,
-} from "../input/InputGroup";
-import Dialog from "./Dialog";
-import DialogContainer from "./DialogContainer";
-
-import "./OperationDialog.css";
-
-export type { InputInitializer, InputValueDict, InputErrorDict };
-
-interface OperationDialogPromptProps {
- message?: Text;
- customMessage?: Text;
- customMessageNode?: ReactNode;
- className?: string;
-}
-
-function OperationDialogPrompt(props: OperationDialogPromptProps) {
- const { message, customMessage, customMessageNode, className } = props;
-
- const c = useC();
-
- return (
- <div className={classNames(className, "cru-operation-dialog-prompt")}>
- {message && <p>{c(message)}</p>}
- {customMessageNode ?? (customMessage != null ? c(customMessage) : null)}
- </div>
- );
-}
-
-export interface OperationDialogProps<TData> {
- open: boolean;
- onClose: () => void;
-
- color?: ThemeColor;
- inputColor?: ThemeColor;
- title: Text;
- inputPrompt?: Text;
- inputPromptNode?: ReactNode;
- successPrompt?: (data: TData) => Text;
- successPromptNode?: (data: TData) => ReactNode;
- failurePrompt?: (error: unknown) => Text;
- failurePromptNode?: (error: unknown) => ReactNode;
-
- inputs: InputInitializer;
-
- onProcess: (inputs: InputValueDict) => Promise<TData>;
- onSuccessAndClose?: (data: TData) => void;
-}
-
-function OperationDialog<TData>(props: OperationDialogProps<TData>) {
- const {
- open,
- onClose,
- color,
- inputColor,
- title,
- inputPrompt,
- inputPromptNode,
- successPrompt,
- successPromptNode,
- failurePrompt,
- failurePromptNode,
- inputs,
- onProcess,
- onSuccessAndClose,
- } = props;
-
- if (process.env.NODE_ENV === "development") {
- if (inputPrompt && inputPromptNode) {
- console.log("InputPrompt and inputPromptNode are both set.");
- }
- if (successPrompt && successPromptNode) {
- console.log("SuccessPrompt and successPromptNode are both set.");
- }
- if (failurePrompt && failurePromptNode) {
- console.log("FailurePrompt and failurePromptNode are both set.");
- }
- }
-
- type Step =
- | { type: "input" }
- | { type: "process" }
- | {
- type: "success";
- data: TData;
- }
- | {
- type: "failure";
- data: unknown;
- };
-
- const [step, setStep] = useState<Step>({ type: "input" });
-
- const { inputGroupProps, hasErrorAndDirty, setAllDisabled, confirm } =
- useInputs({
- init: inputs,
- });
-
- function close() {
- if (step.type !== "process") {
- onClose();
- if (step.type === "success" && onSuccessAndClose) {
- onSuccessAndClose?.(step.data);
- }
- } else {
- console.log("Attempt to close modal dialog when processing.");
- }
- }
-
- function onConfirm() {
- const result = confirm();
- if (result.type === "ok") {
- setStep({ type: "process" });
- setAllDisabled(true);
- onProcess(result.values).then(
- (d) => {
- setStep({
- type: "success",
- data: d,
- });
- },
- (e: unknown) => {
- setStep({
- type: "failure",
- data: e,
- });
- },
- );
- }
- }
-
- let body: ReactNode;
- let buttons: ComponentProps<typeof DialogContainer>["buttons"];
-
- if (step.type === "input" || step.type === "process") {
- const isProcessing = step.type === "process";
-
- body = (
- <div>
- <OperationDialogPrompt
- customMessage={inputPrompt}
- customMessageNode={inputPromptNode}
- />
- <InputGroup
- containerClassName="cru-operation-dialog-input-group"
- color={inputColor ?? "primary"}
- {...inputGroupProps}
- />
- </div>
- );
- buttons = [
- {
- key: "cancel",
- type: "normal",
- props: {
- text: "operationDialog.cancel",
- color: "secondary",
- outline: true,
- onClick: close,
- disabled: isProcessing,
- },
- },
- {
- key: "confirm",
- type: "loading",
- props: {
- text: "operationDialog.confirm",
- color,
- loading: isProcessing,
- disabled: hasErrorAndDirty,
- onClick: onConfirm,
- },
- },
- ];
- } else {
- const result = step;
-
- const promptProps: OperationDialogPromptProps =
- result.type === "success"
- ? {
- message: "operationDialog.success",
- customMessage: successPrompt?.(result.data),
- customMessageNode: successPromptNode?.(result.data),
- }
- : {
- message: "operationDialog.error",
- customMessage: failurePrompt?.(result.data),
- customMessageNode: failurePromptNode?.(result.data),
- };
- body = (
- <div>
- <OperationDialogPrompt {...promptProps} />
- </div>
- );
-
- buttons = [
- {
- key: "ok",
- type: "normal",
- props: {
- text: "operationDialog.ok",
- color: "primary",
- onClick: close,
- },
- },
- ];
- }
-
- return (
- <Dialog open={open} onClose={close}>
- <DialogContainer title={title} titleColor={color} buttons={buttons}>
- {body}
- </DialogContainer>
- </Dialog>
- );
-}
-
-export default OperationDialog;
diff --git a/FrontEnd/src/views/common/dialog/index.ts b/FrontEnd/src/views/common/dialog/index.ts
deleted file mode 100644
index 59f15791..00000000
--- a/FrontEnd/src/views/common/dialog/index.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { useState } from "react";
-
-export { default as Dialog } from "./Dialog";
-export { default as FullPageDialog } from "./FullPageDialog";
-export { default as OperationDialog } from "./OperationDialog";
-export { default as ConfirmDialog } from "./ConfirmDialog";
-
-type DialogMap<D extends string, V> = {
- [K in D]: V;
-};
-
-type DialogKeyMap<D extends string> = DialogMap<D, number>;
-
-type DialogPropsMap<D extends string> = DialogMap<
- D,
- { key: number | string; open: boolean; onClose: () => void }
->;
-
-export function useDialog<D extends string>(
- dialogs: D[],
- options?: {
- initDialog?: D | null;
- onClose?: {
- [K in D]?: () => void;
- };
- },
-): {
- dialog: D | null;
- switchDialog: (newDialog: D | null) => void;
- dialogPropsMap: DialogPropsMap<D>;
- createDialogSwitch: (newDialog: D | null) => () => void;
-} {
- const [dialog, setDialog] = useState<D | null>(options?.initDialog ?? null);
-
- const [dialogKeys, setDialogKeys] = useState<DialogKeyMap<D>>(
- () => Object.fromEntries(dialogs.map((d) => [d, 0])) as DialogKeyMap<D>,
- );
-
- const switchDialog = (newDialog: D | null) => {
- if (dialog !== null) {
- setDialogKeys({ ...dialogKeys, [dialog]: dialogKeys[dialog] + 1 });
- }
- setDialog(newDialog);
- };
-
- return {
- dialog,
- switchDialog,
- dialogPropsMap: Object.fromEntries(
- dialogs.map((d) => [
- d,
- {
- key: `${d}-${dialogKeys[d]}`,
- open: dialog === d,
- onClose: () => {
- switchDialog(null);
- options?.onClose?.[d]?.();
- },
- },
- ]),
- ) as DialogPropsMap<D>,
- createDialogSwitch: (newDialog: D | null) => () => switchDialog(newDialog),
- };
-}
diff --git a/FrontEnd/src/views/common/hooks.ts b/FrontEnd/src/views/common/hooks.ts
deleted file mode 100644
index c1fa5774..00000000
--- a/FrontEnd/src/views/common/hooks.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-// TODO: Migrate hooks
-
-export {
- useIsSmallScreen,
- useClickOutside,
- useScrollToBottom,
-} from "@/utilities/hooks";
-
-import { useMediaQuery } from "react-responsive";
-import { breakpoints } from "./breakpoints";
-
-export function useMobile(): boolean {
- return useMediaQuery({ maxWidth: breakpoints.sm });
-}
diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css
deleted file mode 100644
index a8f5e9a5..00000000
--- a/FrontEnd/src/views/common/index.css
+++ /dev/null
@@ -1,100 +0,0 @@
-@import "./theme.css";
-
-* {
- box-sizing: border-box;
- margin-inline: 0;
- margin-block: 0;
-}
-
-body {
- font-family: var(--cru-default-font-family);
- background: var(--cru-body-background-color);
- color: var(--cru-text-primary-color);
- line-height: 1.2;
-}
-
-.cru-page {
- padding: var(--cru-page-padding);
-}
-
-.cru-page-no-top-padding {
- padding-top: 0;
-}
-
-.cru-text-center {
- text-align: center;
-}
-
-.cru-text-end {
- text-align: end;
-}
-
-.cru-float-left {
- float: left;
-}
-
-.cru-float-right {
- float: right;
-}
-
-.cru-align-text-bottom {
- vertical-align: text-bottom;
-}
-
-.cru-align-middle {
- vertical-align: middle;
-}
-
-.cru-clearfix::after {
- clear: both;
-}
-
-.cru-fill-parent {
- width: 100%;
- height: 100%;
-}
-
-.cru-avatar {
- width: 60px;
- height: 60px;
-}
-
-.cru-avatar.large {
- width: 100px;
- height: 100px;
-}
-
-.cru-avatar.small {
- width: 40px;
- height: 40px;
-}
-
-.cru-round {
- border-radius: 50%;
-}
-
-.cru-tab-pages-action-area {
- display: flex;
- align-items: center;
-}
-
-.alert-container {
- position: fixed;
- z-index: 1070;
-}
-
-@media (min-width: 576px) {
- .alert-container {
- bottom: 0;
- right: 0;
- }
-}
-
-@media (max-width: 575.98px) {
- .alert-container {
- bottom: 0;
- right: 0;
- left: 0;
- text-align: center;
- }
-} \ No newline at end of file
diff --git a/FrontEnd/src/views/common/input/InputGroup.css b/FrontEnd/src/views/common/input/InputGroup.css
deleted file mode 100644
index 7e905b1e..00000000
--- a/FrontEnd/src/views/common/input/InputGroup.css
+++ /dev/null
@@ -1,54 +0,0 @@
-.cru-input-group {
- display: block;
-}
-
-.cru-input-container {
- margin: 0.4em 0;
-}
-
-.cru-input-label {
- display: block;
- color: var(--cru-clickable-normal-color);
- font-size: 0.9em;
- margin-bottom: 0.3em;
-}
-
-.cru-input-label-inline {
- margin-inline-start: 0.5em;
-}
-
-.cru-input-type-text input {
- appearance: none;
- display: block;
- border: 1px solid;
- /* color: var(--cru-surface-on-color); */
- /* background-color: var(--cru-surface-color); */
- margin: 0;
- font-size: 1em;
- padding: 0.2em;
-}
-
-.cru-input-type-text input:hover {
- border-color: var(--cru-clickable-hover-color);
-}
-
-.cru-input-type-text input:focus {
- border-color: var(--cru-clickable-focus-color);
-}
-
-.cru-input-type-text input:disabled {
- border-color: var(--cru-clickable-disabled-color);
-}
-
-.cru-input-error {
- display: block;
- font-size: 0.8em;
- color: var(--cru-danger-color);
- margin-top: 0.4em;
-}
-
-.cru-input-helper {
- display: block;
- font-size: 0.8em;
- color: var(--cru-primary-color);
-} \ No newline at end of file
diff --git a/FrontEnd/src/views/common/input/InputGroup.tsx b/FrontEnd/src/views/common/input/InputGroup.tsx
deleted file mode 100644
index d95bb29e..00000000
--- a/FrontEnd/src/views/common/input/InputGroup.tsx
+++ /dev/null
@@ -1,461 +0,0 @@
-/**
- * Some notes for InputGroup:
- * This is one of the most complicated components in this project.
- * Probably because the feature is complex and involved user inputs.
- *
- * I hope it contains following features:
- * - Input features
- * - Supports a wide range of input types.
- * - Validator to validate user inputs.
- * - Can set initial values.
- * - Dirty, aka, has user touched this input.
- * - Developer friendly
- * - Easy to use APIs.
- * - Type check as much as possible.
- * - UI
- * - Configurable appearance.
- * - Can display helper and error messages.
- * - Easy to extend, like new input types.
- *
- * So here is some design decisions:
- * Inputs are identified by its _key_.
- * `InputGroup` component takes care of only UI and no logic.
- * `useInputs` hook takes care of logic and generate props for `InputGroup`.
- */
-
-import { useState, Ref, useId } from "react";
-import classNames from "classnames";
-
-import { useC, Text, ThemeColor } from "../common";
-
-import "./InputGroup.css";
-
-export interface InputBase {
- key: string;
- label: Text;
- helper?: Text;
- disabled?: boolean;
- error?: Text;
-}
-
-export interface TextInput extends InputBase {
- type: "text";
- value: string;
- password?: boolean;
-}
-
-export interface BoolInput extends InputBase {
- type: "bool";
- value: boolean;
-}
-
-export interface SelectInputOption {
- value: string;
- label: Text;
- icon?: string;
-}
-
-export interface SelectInput extends InputBase {
- type: "select";
- value: string;
- options: SelectInputOption[];
-}
-
-export type Input = TextInput | BoolInput | SelectInput;
-
-export type InputValue = Input["value"];
-
-export type InputValueDict = Record<string, InputValue>;
-export type InputErrorDict = Record<string, Text>;
-export type InputDisabledDict = Record<string, boolean>;
-export type InputDirtyDict = Record<string, boolean>;
-
-export type GeneralInputErrorDict =
- | {
- [key: string]: Text | null | undefined;
- }
- | null
- | undefined;
-
-type MakeInputInfo<I extends Input> = Omit<I, "value" | "error" | "disabled">;
-
-export type InputInfo = {
- [I in Input as I["type"]]: MakeInputInfo<I>;
-}[Input["type"]];
-
-export type Validator = (
- values: InputValueDict,
- inputs: InputInfo[],
-) => GeneralInputErrorDict;
-
-export type InputScheme = {
- inputs: InputInfo[];
- validator?: Validator;
-};
-
-export type InputData = {
- values: InputValueDict;
- errors: InputErrorDict;
- disabled: InputDisabledDict;
- dirties: InputDirtyDict;
-};
-
-export type State = {
- scheme: InputScheme;
- data: InputData;
-};
-
-export type DataInitialization = {
- values?: InputValueDict;
- errors?: GeneralInputErrorDict;
- disabled?: InputDisabledDict;
- dirties?: InputDirtyDict;
-};
-
-export type Initialization = {
- scheme: InputScheme;
- dataInit?: DataInitialization;
-};
-
-export type GeneralInitialization = Initialization | InputScheme | InputInfo[];
-
-export type Initializer = GeneralInitialization | (() => GeneralInitialization);
-
-export interface InputGroupProps {
- color?: ThemeColor;
- containerClassName?: string;
- containerRef?: Ref<HTMLDivElement>;
-
- inputs: Input[];
- onChange: (index: number, value: Input["value"]) => void;
-}
-
-function cleanObject<V>(o: Record<string, V>): Record<string, NonNullable<V>> {
- const result = { ...o };
- for (const key of Object.keys(result)) {
- if (result[key] == null) {
- delete result[key];
- }
- }
- return result as never;
-}
-
-export type ConfirmResult =
- | {
- type: "ok";
- values: InputValueDict;
- }
- | {
- type: "error";
- errors: InputErrorDict;
- };
-
-function validate(
- validator: Validator | null | undefined,
- values: InputValueDict,
- inputs: InputInfo[],
-): InputErrorDict {
- return cleanObject(validator?.(values, inputs) ?? {});
-}
-
-export function useInputs(options: { init: Initializer }): {
- inputGroupProps: InputGroupProps;
- hasError: boolean;
- hasErrorAndDirty: boolean;
- confirm: () => ConfirmResult;
- setAllDisabled: (disabled: boolean) => void;
-} {
- function initializeValue(
- input: InputInfo,
- value?: InputValue | null,
- ): InputValue {
- if (input.type === "text") {
- return value ?? "";
- } else if (input.type === "bool") {
- return value ?? false;
- } else if (input.type === "select") {
- return value ?? input.options[0].value;
- }
- throw new Error("Unknown input type");
- }
-
- function initialize(generalInitialization: GeneralInitialization): State {
- const initialization: Initialization = Array.isArray(generalInitialization)
- ? { scheme: { inputs: generalInitialization } }
- : "scheme" in generalInitialization
- ? generalInitialization
- : { scheme: generalInitialization };
-
- const { scheme, dataInit } = initialization;
- const { inputs, validator } = scheme;
- const keys = inputs.map((input) => input.key);
-
- if (process.env.NODE_ENV === "development") {
- const checkKeys = (dict: Record<string, unknown> | undefined) => {
- if (dict != null) {
- for (const key of Object.keys(dict)) {
- if (!keys.includes(key)) {
- console.warn("");
- }
- }
- }
- };
-
- checkKeys(dataInit?.values);
- checkKeys(dataInit?.errors ?? {});
- checkKeys(dataInit?.disabled);
- checkKeys(dataInit?.dirties);
- }
-
- function clean<V>(
- dict: Record<string, V> | null | undefined,
- ): Record<string, NonNullable<V>> {
- return dict != null ? cleanObject(dict) : {};
- }
-
- const values: InputValueDict = {};
- const disabled: InputDisabledDict = clean(dataInit?.disabled);
- const dirties: InputDirtyDict = clean(dataInit?.dirties);
- const isErrorSet = dataInit?.errors != null;
- let errors: InputErrorDict = clean(dataInit?.errors);
-
- for (let i = 0; i < inputs.length; i++) {
- const input = inputs[i];
- const { key } = input;
-
- values[key] = initializeValue(input, dataInit?.values?.[key]);
- }
-
- if (isErrorSet) {
- if (process.env.NODE_ENV === "development") {
- console.log(
- "You explicitly set errors (not undefined) in initializer, so validator won't run.",
- );
- }
- } else {
- errors = validate(validator, values, inputs);
- }
-
- return {
- scheme,
- data: {
- values,
- errors,
- disabled,
- dirties,
- },
- };
- }
-
- const { init } = options;
- const initializer = typeof init === "function" ? init : () => init;
-
- const [state, setState] = useState<State>(() => initialize(initializer()));
-
- const { scheme, data } = state;
- const { validator } = scheme;
-
- function createAllBooleanDict(value: boolean): Record<string, boolean> {
- const result: InputDirtyDict = {};
- for (const key of scheme.inputs.map((input) => input.key)) {
- result[key] = value;
- }
- return result;
- }
-
- const createAllDirties = () => createAllBooleanDict(true);
-
- const componentInputs: Input[] = [];
-
- for (let i = 0; i < scheme.inputs.length; i++) {
- const input = scheme.inputs[i];
- const value = data.values[input.key];
- const error = data.errors[input.key];
- const disabled = data.disabled[input.key] ?? false;
- const dirty = data.dirties[input.key] ?? false;
- const componentInput: Input = {
- ...input,
- value: value as never,
- disabled,
- error: dirty ? error : undefined,
- };
- componentInputs.push(componentInput);
- }
-
- const hasError = Object.keys(data.errors).length > 0;
- const hasDirty = Object.keys(data.dirties).some((key) => data.dirties[key]);
-
- return {
- inputGroupProps: {
- inputs: componentInputs,
- onChange: (index, value) => {
- const input = scheme.inputs[index];
- const { key } = input;
- const newValues = { ...data.values, [key]: value };
- const newDirties = { ...data.dirties, [key]: true };
- const newErrors = validate(validator, newValues, scheme.inputs);
- setState({
- scheme,
- data: {
- ...data,
- values: newValues,
- errors: newErrors,
- dirties: newDirties,
- },
- });
- },
- },
- hasError,
- hasErrorAndDirty: hasError && hasDirty,
- confirm() {
- const newDirties = createAllDirties();
- const newErrors = validate(validator, data.values, scheme.inputs);
-
- setState({
- scheme,
- data: {
- ...data,
- dirties: newDirties,
- errors: newErrors,
- },
- });
-
- if (Object.keys(newErrors).length !== 0) {
- return {
- type: "error",
- errors: newErrors,
- };
- } else {
- return {
- type: "ok",
- values: data.values,
- };
- }
- },
- setAllDisabled(disabled: boolean) {
- setState({
- scheme,
- data: {
- ...data,
- disabled: createAllBooleanDict(disabled),
- },
- });
- },
- };
-}
-
-export function InputGroup({
- color,
- inputs,
- onChange,
- containerRef,
- containerClassName,
-}: InputGroupProps) {
- const c = useC();
-
- const id = useId();
-
- return (
- <div
- ref={containerRef}
- className={classNames(
- "cru-input-group",
- `cru-clickable-${color ?? "primary"}`,
- containerClassName,
- )}
- >
- {inputs.map((item, index) => {
- const { key, type, value, label, error, helper, disabled } = item;
-
- const getContainerClassName = (
- ...additionalClassNames: classNames.ArgumentArray
- ) =>
- classNames(
- `cru-input-container cru-input-type-${type}`,
- error && "error",
- ...additionalClassNames,
- );
-
- const changeValue = (value: InputValue) => {
- onChange(index, value);
- };
-
- const inputId = `${id}-${key}`;
-
- if (type === "text") {
- const { password } = item;
- return (
- <div
- key={key}
- className={getContainerClassName(password && "password")}
- >
- {label && (
- <label className="cru-input-label" htmlFor={inputId}>
- {c(label)}
- </label>
- )}
- <input
- id={inputId}
- type={password ? "password" : "text"}
- value={value}
- onChange={(event) => {
- const v = event.target.value;
- changeValue(v);
- }}
- disabled={disabled}
- />
- {error && <div className="cru-input-error">{c(error)}</div>}
- {helper && <div className="cru-input-helper">{c(helper)}</div>}
- </div>
- );
- } else if (type === "bool") {
- return (
- <div key={key} className={getContainerClassName()}>
- <input
- id={inputId}
- type="checkbox"
- checked={value}
- onChange={(event) => {
- const v = event.currentTarget.checked;
- changeValue(v);
- }}
- disabled={disabled}
- />
- <label className="cru-input-label-inline" htmlFor={inputId}>
- {c(label)}
- </label>
- {error && <div className="cru-input-error">{c(error)}</div>}
- {helper && <div className="cru-input-helper">{c(helper)}</div>}
- </div>
- );
- } else if (type === "select") {
- return (
- <div key={key} className={getContainerClassName()}>
- <label className="cru-input-label" htmlFor={inputId}>
- {c(label)}
- </label>
- <select
- id={inputId}
- value={value}
- onChange={(event) => {
- const e = event.target.value;
- changeValue(e);
- }}
- disabled={disabled}
- >
- {item.options.map((option) => {
- return (
- <option value={option.value} key={option.value}>
- {option.icon}
- {c(option.label)}
- </option>
- );
- })}
- </select>
- </div>
- );
- }
- })}
- </div>
- );
-}
diff --git a/FrontEnd/src/views/common/list/ListContainer.css b/FrontEnd/src/views/common/list/ListContainer.css
deleted file mode 100644
index 53781834..00000000
--- a/FrontEnd/src/views/common/list/ListContainer.css
+++ /dev/null
@@ -1,4 +0,0 @@
-.cru-list-container {
- border: 1px solid var(--cru-clickable-primary-normal-color);
- border-radius: 5px;
-}
diff --git a/FrontEnd/src/views/common/list/ListContainer.tsx b/FrontEnd/src/views/common/list/ListContainer.tsx
deleted file mode 100644
index aa00d12c..00000000
--- a/FrontEnd/src/views/common/list/ListContainer.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { ComponentPropsWithoutRef, forwardRef, Ref } from "react";
-import classNames from "classnames";
-
-import "./ListContainer.css"
-
-function _ListContainer(
- { className, children, ...otherProps }: ComponentPropsWithoutRef<"div">,
- ref: Ref<HTMLDivElement>,
-) {
- return (
- <div
- ref={ref}
- className={classNames("cru-list-container", className)}
- {...otherProps}
- >
- {children}
- </div>
- );
-}
-
-const ListContainer = forwardRef(_ListContainer);
-
-export default ListContainer;
diff --git a/FrontEnd/src/views/common/list/ListItemContainer.css b/FrontEnd/src/views/common/list/ListItemContainer.css
deleted file mode 100644
index 8d7afa9f..00000000
--- a/FrontEnd/src/views/common/list/ListItemContainer.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.cru-list-item-container {
- border: 1px solid var(--cru-clickable-primary-normal-color);
-}
diff --git a/FrontEnd/src/views/common/list/ListItemContainer.tsx b/FrontEnd/src/views/common/list/ListItemContainer.tsx
deleted file mode 100644
index 315cbd6e..00000000
--- a/FrontEnd/src/views/common/list/ListItemContainer.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { ComponentPropsWithoutRef, forwardRef, Ref } from "react";
-import classNames from "classnames";
-
-import "./ListItemContainer.css";
-
-function _ListItemContainer(
- { className, children, ...otherProps }: ComponentPropsWithoutRef<"div">,
- ref: Ref<HTMLDivElement>,
-) {
- return (
- <div
- ref={ref}
- className={classNames("cru-list-item-container", className)}
- {...otherProps}
- >
- {children}
- </div>
- );
-}
-
-const ListItemContainer = forwardRef(_ListItemContainer);
-
-export default ListItemContainer;
diff --git a/FrontEnd/src/views/common/list/index.ts b/FrontEnd/src/views/common/list/index.ts
deleted file mode 100644
index e183f7da..00000000
--- a/FrontEnd/src/views/common/list/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import ListContainer from "./ListContainer";
-import ListItemContainer from "./ListItemContainer";
-
-export { ListContainer, ListItemContainer };
diff --git a/FrontEnd/src/views/common/menu/Menu.css b/FrontEnd/src/views/common/menu/Menu.css
deleted file mode 100644
index 75734533..00000000
--- a/FrontEnd/src/views/common/menu/Menu.css
+++ /dev/null
@@ -1,36 +0,0 @@
-.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/views/common/menu/Menu.tsx b/FrontEnd/src/views/common/menu/Menu.tsx
deleted file mode 100644
index e8099c76..00000000
--- a/FrontEnd/src/views/common/menu/Menu.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-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 (
- <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={() => {
- onClick();
- onItemClicked?.();
- }}
- >
- {icon != null && <Icon color={color} icon={icon} />}
- {c(text)}
- </button>
- );
- }
- })}
- </div>
- );
-}
diff --git a/FrontEnd/src/views/common/menu/PopupMenu.css b/FrontEnd/src/views/common/menu/PopupMenu.css
deleted file mode 100644
index 149e0699..00000000
--- a/FrontEnd/src/views/common/menu/PopupMenu.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.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/views/common/menu/PopupMenu.tsx b/FrontEnd/src/views/common/menu/PopupMenu.tsx
deleted file mode 100644
index 5c8d5e98..00000000
--- a/FrontEnd/src/views/common/menu/PopupMenu.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { useState, CSSProperties, ReactNode } from "react";
-import classNames from "classnames";
-import { createPortal } from "react-dom";
-import { usePopper } from "react-popper";
-
-import { useClickOutside } from "@/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<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}
- onItemClicked={() => {
- setShow(false);
- }}
- />
- </div>,
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- document.getElementById("portal")!,
- )}
- </div>
- );
-}
diff --git a/FrontEnd/src/views/common/tab/TabPages.tsx b/FrontEnd/src/views/common/tab/TabPages.tsx
deleted file mode 100644
index cdb988e0..00000000
--- a/FrontEnd/src/views/common/tab/TabPages.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import * as React from "react";
-
-import { I18nText, UiLogicError } from "@/common";
-
-import Tabs from "./Tabs";
-
-export interface TabPage {
- name: string;
- text: I18nText;
- page: React.ReactNode;
-}
-
-export interface TabPagesProps {
- pages: TabPage[];
- actions?: React.ReactNode;
- dense?: boolean;
- className?: string;
- style?: React.CSSProperties;
- navClassName?: string;
- navStyle?: React.CSSProperties;
- pageContainerClassName?: string;
- pageContainerStyle?: React.CSSProperties;
-}
-
-const TabPages: React.FC<TabPagesProps> = ({
- pages,
- actions,
- dense,
- className,
- style,
- navClassName,
- navStyle,
- pageContainerClassName,
- pageContainerStyle,
-}) => {
- if (pages.length === 0) {
- throw new UiLogicError("Page list can't be empty.");
- }
-
- const [tab, setTab] = React.useState<string>(pages[0].name);
-
- const currentPage = pages.find((p) => p.name === tab);
-
- if (currentPage == null) {
- throw new UiLogicError("Current tab value is bad.");
- }
-
- return (
- <div className={className} style={style}>
- <Tabs
- tabs={pages.map((page) => ({
- name: page.name,
- text: page.text,
- onClick: () => {
- setTab(page.name);
- },
- }))}
- dense={dense}
- activeTabName={tab}
- className={navClassName}
- style={navStyle}
- actions={actions}
- />
- <div className={pageContainerClassName} style={pageContainerStyle}>
- {currentPage.page}
- </div>
- </div>
- );
-};
-
-export default TabPages;
diff --git a/FrontEnd/src/views/common/tab/Tabs.css b/FrontEnd/src/views/common/tab/Tabs.css
deleted file mode 100644
index 395d16a7..00000000
--- a/FrontEnd/src/views/common/tab/Tabs.css
+++ /dev/null
@@ -1,33 +0,0 @@
-.cru-nav {
- border-bottom: var(--cru-primary-color) 1px solid;
- display: flex;
-}
-
-.cru-nav-item {
- color: var(--cru-primary-color);
- border: var(--cru-background-2-color) 0.5px solid;
- border-bottom: none;
- padding: 0.5em 1.5em;
- border-top-left-radius: 5px;
- border-top-right-radius: 5px;
- transition: all 0.5s;
- cursor: pointer;
-}
-
-.cru-nav.dense .cru-nav-item {
- padding: 0.2em 1em;
-}
-
-.cru-nav-item:hover {
- background-color: var(--cru-background-1-color);
-}
-
-.cru-nav-item.active {
- color: var(--cru-primary-t-color);
- background-color: var(--cru-primary-color);
- border-color: var(--cru-primary-color);
-}
-
-.cru-nav-action-area {
- margin-left: auto;
-}
diff --git a/FrontEnd/src/views/common/tab/Tabs.tsx b/FrontEnd/src/views/common/tab/Tabs.tsx
deleted file mode 100644
index 3e3ef6fa..00000000
--- a/FrontEnd/src/views/common/tab/Tabs.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import * as React from "react";
-import { Link } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-import classnames from "classnames";
-
-import { convertI18nText, I18nText } from "@/common";
-
-import "./Tabs.css";
-
-export interface Tab {
- name: string;
- text: I18nText;
- link?: string;
- onClick?: () => void;
-}
-
-export interface TabsProps {
- activeTabName?: string;
- actions?: React.ReactNode;
- dense?: boolean;
- tabs: Tab[];
- className?: string;
- style?: React.CSSProperties;
-}
-
-export default function Tabs(props: TabsProps): React.ReactElement | null {
- const { tabs, activeTabName, className, style, dense, actions } = props;
-
- const { t } = useTranslation();
-
- return (
- <div
- className={classnames("cru-nav", dense && "dense", className)}
- style={style}
- >
- {tabs.map((tab) => {
- const active = activeTabName === tab.name;
- const className = classnames("cru-nav-item", active && "active");
-
- if (tab.link != null) {
- return (
- <Link
- key={tab.name}
- to={tab.link}
- onClick={tab.onClick}
- className={className}
- >
- {convertI18nText(tab.text, t)}
- </Link>
- );
- } else {
- return (
- <span key={tab.name} onClick={tab.onClick} className={className}>
- {convertI18nText(tab.text, t)}
- </span>
- );
- }
- })}
- <div className="cru-nav-action-area">{actions}</div>
- </div>
- );
-}
diff --git a/FrontEnd/src/views/common/theme-color.css b/FrontEnd/src/views/common/theme-color.css
deleted file mode 100644
index 24a7e267..00000000
--- a/FrontEnd/src/views/common/theme-color.css
+++ /dev/null
@@ -1,173 +0,0 @@
-/* Generated by theme-generator.ts */
-
-:root {
- --cru-primary-color: hsl(210 100% 40%);
- --cru-primary-1-color: hsl(210 100% 37%);
- --cru-primary-2-color: hsl(210 100% 34%);
- --cru-primary-on-color: hsl(210 100% 100%);
- --cru-primary-container-color: hsl(210 100% 90%);
- --cru-primary-container-1-color: hsl(210 100% 80%);
- --cru-primary-container-2-color: hsl(210 100% 70%);
- --cru-primary-on-container-color: hsl(210 100% 10%);
- --cru-secondary-color: hsl(40 100% 40%);
- --cru-secondary-1-color: hsl(40 100% 37%);
- --cru-secondary-2-color: hsl(40 100% 34%);
- --cru-secondary-on-color: hsl(40 100% 100%);
- --cru-secondary-container-color: hsl(40 100% 90%);
- --cru-secondary-container-1-color: hsl(40 100% 80%);
- --cru-secondary-container-2-color: hsl(40 100% 70%);
- --cru-secondary-on-container-color: hsl(40 100% 10%);
- --cru-tertiary-color: hsl(160 100% 40%);
- --cru-tertiary-1-color: hsl(160 100% 37%);
- --cru-tertiary-2-color: hsl(160 100% 34%);
- --cru-tertiary-on-color: hsl(160 100% 100%);
- --cru-tertiary-container-color: hsl(160 100% 90%);
- --cru-tertiary-container-1-color: hsl(160 100% 80%);
- --cru-tertiary-container-2-color: hsl(160 100% 70%);
- --cru-tertiary-on-container-color: hsl(160 100% 10%);
- --cru-danger-color: hsl(0 100% 40%);
- --cru-danger-1-color: hsl(0 100% 37%);
- --cru-danger-2-color: hsl(0 100% 34%);
- --cru-danger-on-color: hsl(0 100% 100%);
- --cru-danger-container-color: hsl(0 100% 90%);
- --cru-danger-container-1-color: hsl(0 100% 80%);
- --cru-danger-container-2-color: hsl(0 100% 70%);
- --cru-danger-on-container-color: hsl(0 100% 10%);
- --cru-success-color: hsl(120 60% 40%);
- --cru-success-1-color: hsl(120 60% 37%);
- --cru-success-2-color: hsl(120 60% 34%);
- --cru-success-on-color: hsl(120 60% 100%);
- --cru-success-container-color: hsl(120 60% 90%);
- --cru-success-container-1-color: hsl(120 60% 80%);
- --cru-success-container-2-color: hsl(120 60% 70%);
- --cru-success-on-container-color: hsl(120 60% 10%);
- --cru-surface-dim-color: hsl(0 0% 87%);
- --cru-surface-color: hsl(0 0% 98%);
- --cru-surface-1-color: hsl(0 0% 90%);
- --cru-surface-2-color: hsl(0 0% 82%);
- --cru-surface-bright-color: hsl(0 0% 98%);
- --cru-surface-container-lowest-color: hsl(0 0% 100%);
- --cru-surface-container-low-color: hsl(0 0% 96%);
- --cru-surface-container-color: hsl(0 0% 94%);
- --cru-surface-container-high-color: hsl(0 0% 92%);
- --cru-surface-container-highest-color: hsl(0 0% 90%);
- --cru-surface-on-color: hsl(0 0% 10%);
- --cru-surface-on-variant-color: hsl(0 0% 30%);
- --cru-surface-outline-color: hsl(0 0% 50%);
- --cru-surface-outline-variant-color: hsl(0 0% 80%);
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --cru-primary-color: hsl(210 100% 80%);
- --cru-primary-1-color: hsl(210 100% 75%);
- --cru-primary-2-color: hsl(210 100% 68%);
- --cru-primary-on-color: hsl(210 100% 20%);
- --cru-primary-container-color: hsl(210 100% 30%);
- --cru-primary-container-1-color: hsl(210 100% 25%);
- --cru-primary-container-2-color: hsl(210 100% 20%);
- --cru-primary-on-container-color: hsl(210 100% 90%);
- --cru-secondary-color: hsl(40 100% 80%);
- --cru-secondary-1-color: hsl(40 100% 75%);
- --cru-secondary-2-color: hsl(40 100% 68%);
- --cru-secondary-on-color: hsl(40 100% 20%);
- --cru-secondary-container-color: hsl(40 100% 30%);
- --cru-secondary-container-1-color: hsl(40 100% 25%);
- --cru-secondary-container-2-color: hsl(40 100% 20%);
- --cru-secondary-on-container-color: hsl(40 100% 90%);
- --cru-tertiary-color: hsl(160 100% 80%);
- --cru-tertiary-1-color: hsl(160 100% 75%);
- --cru-tertiary-2-color: hsl(160 100% 68%);
- --cru-tertiary-on-color: hsl(160 100% 20%);
- --cru-tertiary-container-color: hsl(160 100% 30%);
- --cru-tertiary-container-1-color: hsl(160 100% 25%);
- --cru-tertiary-container-2-color: hsl(160 100% 20%);
- --cru-tertiary-on-container-color: hsl(160 100% 90%);
- --cru-danger-color: hsl(0 100% 80%);
- --cru-danger-1-color: hsl(0 100% 75%);
- --cru-danger-2-color: hsl(0 100% 68%);
- --cru-danger-on-color: hsl(0 100% 20%);
- --cru-danger-container-color: hsl(0 100% 30%);
- --cru-danger-container-1-color: hsl(0 100% 25%);
- --cru-danger-container-2-color: hsl(0 100% 20%);
- --cru-danger-on-container-color: hsl(0 100% 90%);
- --cru-success-color: hsl(120 60% 80%);
- --cru-success-1-color: hsl(120 60% 75%);
- --cru-success-2-color: hsl(120 60% 68%);
- --cru-success-on-color: hsl(120 60% 20%);
- --cru-success-container-color: hsl(120 60% 30%);
- --cru-success-container-1-color: hsl(120 60% 25%);
- --cru-success-container-2-color: hsl(120 60% 20%);
- --cru-success-on-container-color: hsl(120 60% 90%);
- --cru-surface-dim-color: hsl(0 0% 6%);
- --cru-surface-color: hsl(0 0% 6%);
- --cru-surface-1-color: hsl(0 0% 25%);
- --cru-surface-2-color: hsl(0 0% 40%);
- --cru-surface-bright-color: hsl(0 0% 24%);
- --cru-surface-container-lowest-color: hsl(0 0% 4%);
- --cru-surface-container-low-color: hsl(0 0% 10%);
- --cru-surface-container-color: hsl(0 0% 12%);
- --cru-surface-container-high-color: hsl(0 0% 17%);
- --cru-surface-container-highest-color: hsl(0 0% 22%);
- --cru-surface-on-color: hsl(0 0% 90%);
- --cru-surface-on-variant-color: hsl(0 0% 80%);
- --cru-surface-outline-color: hsl(0 0% 60%);
- --cru-surface-outline-variant-color: hsl(0 0% 30%);
- }
-}
-
-.cru-primary {
- --cru-key-color: var(--cru-primary-color);
- --cru-key-1-color: var(--cru-primary-1-color);
- --cru-key-2-color: var(--cru-primary-2-color);
- --cru-key-on-color: var(--cru-primary-on-color);
- --cru-key-container-color: var(--cru-primary-container-color);
- --cru-key-container-1-color: var(--cru-primary-container-1-color);
- --cru-key-container-2-color: var(--cru-primary-container-2-color);
- --cru-key-on-container-color: var(--cru-primary-on-container-color);
-}
-
-.cru-secondary {
- --cru-key-color: var(--cru-secondary-color);
- --cru-key-1-color: var(--cru-secondary-1-color);
- --cru-key-2-color: var(--cru-secondary-2-color);
- --cru-key-on-color: var(--cru-secondary-on-color);
- --cru-key-container-color: var(--cru-secondary-container-color);
- --cru-key-container-1-color: var(--cru-secondary-container-1-color);
- --cru-key-container-2-color: var(--cru-secondary-container-2-color);
- --cru-key-on-container-color: var(--cru-secondary-on-container-color);
-}
-
-.cru-tertiary {
- --cru-key-color: var(--cru-tertiary-color);
- --cru-key-1-color: var(--cru-tertiary-1-color);
- --cru-key-2-color: var(--cru-tertiary-2-color);
- --cru-key-on-color: var(--cru-tertiary-on-color);
- --cru-key-container-color: var(--cru-tertiary-container-color);
- --cru-key-container-1-color: var(--cru-tertiary-container-1-color);
- --cru-key-container-2-color: var(--cru-tertiary-container-2-color);
- --cru-key-on-container-color: var(--cru-tertiary-on-container-color);
-}
-
-.cru-danger {
- --cru-key-color: var(--cru-danger-color);
- --cru-key-1-color: var(--cru-danger-1-color);
- --cru-key-2-color: var(--cru-danger-2-color);
- --cru-key-on-color: var(--cru-danger-on-color);
- --cru-key-container-color: var(--cru-danger-container-color);
- --cru-key-container-1-color: var(--cru-danger-container-1-color);
- --cru-key-container-2-color: var(--cru-danger-container-2-color);
- --cru-key-on-container-color: var(--cru-danger-on-container-color);
-}
-
-.cru-success {
- --cru-key-color: var(--cru-success-color);
- --cru-key-1-color: var(--cru-success-1-color);
- --cru-key-2-color: var(--cru-success-2-color);
- --cru-key-on-color: var(--cru-success-on-color);
- --cru-key-container-color: var(--cru-success-container-color);
- --cru-key-container-1-color: var(--cru-success-container-1-color);
- --cru-key-container-2-color: var(--cru-success-container-2-color);
- --cru-key-on-container-color: var(--cru-success-on-container-color);
-}
-
diff --git a/FrontEnd/src/views/common/theme.css b/FrontEnd/src/views/common/theme.css
deleted file mode 100644
index 6ceb369f..00000000
--- a/FrontEnd/src/views/common/theme.css
+++ /dev/null
@@ -1,146 +0,0 @@
-@import "./theme-color.css";
-
-:root {
- --cru-default-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
- --cru-page-padding: 1em 2em;
-
- --cru-border-radius: 4px;
- --cru-card-border-radius: 4px;
-}
-
-/* theme colors */
-:root {
- --cru-primary-color: hsl(210, 100%, 50%);
- --cru-secondary-color: hsl(30, 100%, 50%);
- --cru-create-color: hsl(120, 100%, 25%);
- --cru-danger-color: hsl(0, 100%, 50%);
-}
-
-/* common colors */
-:root {
- --cru-background-color: hsl(0, 0%, 100%);
- --cru-container-background-color: hsl(0, 0%, 97%);
- --cru-text-primary-color: hsl(0, 0%, 0%);
- --cru-text-secondary-color: hsl(0, 0%, 38%);
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --cru-background-color: hsl(0, 0%, 0%);
- --cru-container-background-color: hsl(0, 0%, 2%);
- --cru-text-primary-color: hsl(0, 0%, 100%);
- --cru-text-secondary-color: hsl(0, 0%, 85%);
- }
-}
-
-:root {
- --cru-body-background-color: var(--cru-background-color);
-}
-
-/* clickable color */
-:root {
- --cru-clickable-primary-normal-color: var(--cru-primary-color);
- --cru-clickable-primary-hover-color: hsl(210, 100%, 60%);
- --cru-clickable-primary-focus-color: hsl(210, 100%, 60%);
- --cru-clickable-primary-active-color: hsl(210, 100%, 70%);
- --cru-clickable-secondary-normal-color: var(--cru-secondary-color);
- --cru-clickable-secondary-hover-color: hsl(30, 100%, 60%);
- --cru-clickable-secondary-focus-color: hsl(30, 100%, 60%);
- --cru-clickable-secondary-active-color: hsl(30, 100%, 70%);
- --cru-clickable-create-normal-color: var(--cru-create-color);
- --cru-clickable-create-hover-color: hsl(120, 100%, 35%);
- --cru-clickable-create-focus-color: hsl(120, 100%, 35%);
- --cru-clickable-create-active-color: hsl(120, 100%, 35%);
- --cru-clickable-danger-normal-color: var(--cru-danger-color);
- --cru-clickable-danger-hover-color: hsl(0, 100%, 60%);
- --cru-clickable-danger-focus-color: hsl(0, 100%, 60%);
- --cru-clickable-danger-active-color: hsl(0, 100%, 70%);
- --cru-clickable-grayscale-normal-color: hsl(0, 0%, 100%);
- --cru-clickable-grayscale-hover-color: hsl(0, 0%, 92%);
- --cru-clickable-grayscale-focus-color: hsl(0, 0%, 92%);
- --cru-clickable-grayscale-active-color: hsl(0, 0%, 88%);
- --cru-clickable-disabled-color: hsl(0, 0%, 50%);
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --cru-clickable-grayscale-normal-color: hsl(0, 0%, 0%);
- --cru-clickable-grayscale-hover-color: hsl(0, 0%, 10%);
- --cru-clickable-grayscale-focus-color: hsl(0, 0%, 10%);
- --cru-clickable-grayscale-active-color: hsl(0, 0%, 20%);
- }
-}
-
-.cru-clickable-primary {
- --cru-clickable-normal-color: var(--cru-clickable-primary-normal-color);
- --cru-clickable-hover-color: var(--cru-clickable-primary-hover-color);
- --cru-clickable-focus-color: var(--cru-clickable-primary-focus-color);
- --cru-clickable-active-color: var(--cru-clickable-primary-active-color);
-}
-
-.cru-clickable-secondary {
- --cru-clickable-normal-color: var(--cru-clickable-secondary-normal-color);
- --cru-clickable-hover-color: var(--cru-clickable-secondary-hover-color);
- --cru-clickable-focus-color: var(--cru-clickable-secondary-focus-color);
- --cru-clickable-active-color: var(--cru-clickable-secondary-active-color);
-}
-
-.cru-clickable-create {
- --cru-clickable-normal-color: var(--cru-clickable-create-normal-color);
- --cru-clickable-hover-color: var(--cru-clickable-create-hover-color);
- --cru-clickable-focus-color: var(--cru-clickable-create-focus-color);
- --cru-clickable-active-color: var(--cru-clickable-create-active-color);
-}
-
-.cru-clickable-danger {
- --cru-clickable-normal-color: var(--cru-clickable-danger-normal-color);
- --cru-clickable-hover-color: var(--cru-clickable-danger-hover-color);
- --cru-clickable-focus-color: var(--cru-clickable-danger-focus-color);
- --cru-clickable-active-color: var(--cru-clickable-danger-active-color);
-}
-
-.cru-clickable-grayscale {
- --cru-clickable-normal-color: var(--cru-clickable-grayscale-normal-color);
- --cru-clickable-hover-color: var(--cru-clickable-grayscale-hover-color);
- --cru-clickable-focus-color: var(--cru-clickable-grayscale-focus-color);
- --cru-clickable-active-color: var(--cru-clickable-grayscale-active-color);
-}
-
-/* button colors */
-:root {
- /* push button colors */
- --cru-push-button-text-color: #ffffff;
- --cru-push-button-disabled-text-color: hsl(0, 0%, 80%);
-}
-
-/* Card colors */
-:root {
- --cru-card-background-primary-color: hsl(210, 100%, 50%);
- --cru-card-border-primary-color: hsl(210, 100%, 50%);
- --cru-card-background-secondary-color: hsl(30, 100%, 50%);
- --cru-card-border-secondary-color: hsl(30, 100%, 50%);
- --cru-card-background-create-color: hsl(120, 100%, 25%);
- --cru-card-border-create-color: hsl(120, 100%, 25%);
- --cru-card-background-danger-color: hsl(0, 100%, 50%);
- --cru-card-border-danger-color: hsl(0, 100%, 50%);
-}
-
-.cru-card-primary {
- --cru-card-background-color: var(--cru-card-background-primary-color);
- --cru-card-border-color: var(--cru-card-border-primary-color)
-}
-
-.cru-card-secondary {
- --cru-card-background-color: var(--cru-card-background-secondary-color);
- --cru-card-border-color: var(--cru-card-border-secondary-color)
-}
-
-.cru-card-create {
- --cru-card-background-color: var(--cru-card-background-create-color);
- --cru-card-border-color: var(--cru-card-border-create-color)
-}
-
-.cru-card-danger {
- --cru-card-background-color: var(--cru-card-background-danger-color);
- --cru-card-border-color: var(--cru-card-border-danger-color)
-} \ No newline at end of file
diff --git a/FrontEnd/src/views/common/user/UserAvatar.tsx b/FrontEnd/src/views/common/user/UserAvatar.tsx
deleted file mode 100644
index aea7bd48..00000000
--- a/FrontEnd/src/views/common/user/UserAvatar.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Ref, ComponentPropsWithoutRef } from "react";
-
-import { getHttpUserClient } from "@/http/user";
-
-export interface UserAvatarProps extends ComponentPropsWithoutRef<"img"> {
- username: string;
- imgRef?: Ref<HTMLImageElement> | null;
-}
-
-export default function UserAvatar({
- username,
- imgRef,
- ...otherProps
-}: UserAvatarProps) {
- return (
- <img
- ref={imgRef}
- src={getHttpUserClient().generateAvatarUrl(username)}
- {...otherProps}
- />
- );
-}