diff options
Diffstat (limited to 'FrontEnd/src/views')
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} - /> - ); -} |