From fa540c046d126449f77e46edd379bbc84e02d05d Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 26 Jun 2021 00:00:54 +0800 Subject: ... --- FrontEnd/src/views/common/button/Button.css | 0 FrontEnd/src/views/common/button/Button.tsx | 30 ++++++++++++++++++++++++ FrontEnd/src/views/common/button/FlatButton.tsx | 31 ++++--------------------- FrontEnd/src/views/common/button/TextButton.tsx | 31 ++++--------------------- FrontEnd/src/views/common/button/common.ts | 31 +++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 54 deletions(-) create mode 100644 FrontEnd/src/views/common/button/Button.css create mode 100644 FrontEnd/src/views/common/button/Button.tsx create mode 100644 FrontEnd/src/views/common/button/common.ts (limited to 'FrontEnd/src/views/common/button') diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css new file mode 100644 index 00000000..e69de29b diff --git a/FrontEnd/src/views/common/button/Button.tsx b/FrontEnd/src/views/common/button/Button.tsx new file mode 100644 index 00000000..11710647 --- /dev/null +++ b/FrontEnd/src/views/common/button/Button.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; + +import { calculateProps, CommonButtonProps } from "./common"; + +import "./Button.css"; + +function _Button( + props: CommonButtonProps & { customButtonClassName?: string }, + ref: React.ForwardedRef +): React.ReactElement | null { + const { t } = useTranslation(); + + const { customButtonClassName, ...otherProps } = props; + + const { newProps, children } = calculateProps( + otherProps, + customButtonClassName ?? "cru-button", + t + ); + + return ( + + ); +} + +const Button = React.forwardRef(_Button); +export default Button; diff --git a/FrontEnd/src/views/common/button/FlatButton.tsx b/FrontEnd/src/views/common/button/FlatButton.tsx index 6351971a..266ea908 100644 --- a/FrontEnd/src/views/common/button/FlatButton.tsx +++ b/FrontEnd/src/views/common/button/FlatButton.tsx @@ -1,39 +1,16 @@ import React from "react"; -import { useTranslation } from "react-i18next"; -import classNames from "classnames"; -import { convertI18nText, I18nText } from "@/common"; -import { PaletteColorType } from "@/palette"; +import { CommonButtonProps } from "./common"; +import Button from "./Button"; import "./FlatButton.css"; function _FlatButton( - { - text, - color, - onClick, - className, - style, - }: { - text: I18nText; - color?: PaletteColorType; - onClick?: (e: React.MouseEvent) => void; - className?: string; - style?: React.CSSProperties; - }, + props: CommonButtonProps, ref: React.ForwardedRef ): React.ReactElement | null { - const { t } = useTranslation(); - return ( - + + - ); -}; - -export default LoadingButton; diff --git a/FrontEnd/src/views/common/OperationDialog.tsx b/FrontEnd/src/views/common/OperationDialog.tsx deleted file mode 100644 index ac4c51b9..00000000 --- a/FrontEnd/src/views/common/OperationDialog.tsx +++ /dev/null @@ -1,471 +0,0 @@ -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { Form, Button, Modal } from "react-bootstrap"; -import { TwitterPicker } from "react-color"; -import moment from "moment"; - -import { convertI18nText, I18nText, UiLogicError } from "@/common"; - -import LoadingButton from "./LoadingButton"; - -interface DefaultErrorPromptProps { - error?: string; -} - -const DefaultErrorPrompt: React.FC = (props) => { - const { t } = useTranslation(); - - let result =

{t("operationDialog.error")}

; - - if (props.error != null) { - result = ( - <> - {result} -

{props.error}

- - ); - } - - return result; -}; - -export interface OperationDialogTextInput { - type: "text"; - label?: I18nText; - password?: boolean; - initValue?: string; - textFieldProps?: Omit< - React.InputHTMLAttributes, - "type" | "value" | "onChange" | "aria-relevant" - >; - helperText?: string; -} - -export interface OperationDialogBoolInput { - type: "bool"; - label: I18nText; - initValue?: boolean; -} - -export interface OperationDialogSelectInputOption { - value: string; - label: I18nText; - icon?: React.ReactElement; -} - -export interface OperationDialogSelectInput { - type: "select"; - label: I18nText; - options: OperationDialogSelectInputOption[]; - initValue?: string; -} - -export interface OperationDialogColorInput { - type: "color"; - label?: I18nText; - initValue?: string | null; - canBeNull?: boolean; -} - -export interface OperationDialogDateTimeInput { - type: "datetime"; - label?: I18nText; - initValue?: string; -} - -export type OperationDialogInput = - | OperationDialogTextInput - | OperationDialogBoolInput - | OperationDialogSelectInput - | OperationDialogColorInput - | OperationDialogDateTimeInput; - -interface OperationInputTypeStringToValueTypeMap { - text: string; - bool: boolean; - select: string; - color: string | null; - datetime: string; -} - -type MapOperationInputTypeStringToValueType = - Type extends keyof OperationInputTypeStringToValueTypeMap - ? OperationInputTypeStringToValueTypeMap[Type] - : never; - -type MapOperationInputInfoValueType = T extends OperationDialogInput - ? MapOperationInputTypeStringToValueType - : T; - -const initValueMapperMap: { - [T in OperationDialogInput as T["type"]]: ( - item: T - ) => MapOperationInputInfoValueType; -} = { - bool: (item) => item.initValue ?? false, - color: (item) => item.initValue ?? null, - datetime: (item) => { - if (item.initValue != null) { - return moment(item.initValue).format("YYYY-MM-DDTHH:mm:ss"); - } else { - return ""; - } - }, - select: (item) => item.initValue ?? item.options[0].value, - text: (item) => item.initValue ?? "", -}; - -type MapOperationInputInfoValueTypeList< - Tuple extends readonly OperationDialogInput[] -> = { - [Index in keyof Tuple]: MapOperationInputInfoValueType; -} & { length: Tuple["length"] }; - -export type OperationInputError = - | { - [index: number]: I18nText | null | undefined; - } - | null - | undefined; - -const isNoError = (error: OperationInputError): boolean => { - if (error == null) return true; - for (const key in error) { - if (error[key] != null) return false; - } - return true; -}; - -export interface OperationDialogProps< - TData, - OperationInputInfoList extends readonly OperationDialogInput[] -> { - open: boolean; - close: () => void; - title: I18nText | (() => React.ReactNode); - themeColor?: "danger" | "success" | string; - onProcess: ( - inputs: MapOperationInputInfoValueTypeList - ) => Promise; - inputScheme?: OperationInputInfoList; - inputValidator?: ( - inputs: MapOperationInputInfoValueTypeList - ) => OperationInputError; - inputPrompt?: I18nText | (() => React.ReactNode); - processPrompt?: () => React.ReactNode; - successPrompt?: (data: TData) => React.ReactNode; - failurePrompt?: (error: unknown) => React.ReactNode; - onSuccessAndClose?: (data: TData) => void; -} - -const OperationDialog = < - TData, - OperationInputInfoList extends readonly OperationDialogInput[] ->( - props: OperationDialogProps -): React.ReactElement => { - const inputScheme = (props.inputScheme ?? - []) as readonly OperationDialogInput[]; - - const { t } = useTranslation(); - - type Step = - | { type: "input" } - | { type: "process" } - | { - type: "success"; - data: TData; - } - | { - type: "failure"; - data: unknown; - }; - const [step, setStep] = useState({ type: "input" }); - - type ValueType = boolean | string | null | undefined; - - const [values, setValues] = useState( - inputScheme.map((item) => { - if (item.type in initValueMapperMap) { - return ( - initValueMapperMap[item.type] as ( - i: OperationDialogInput - ) => ValueType - )(item); - } else { - throw new UiLogicError("Unknown input scheme."); - } - }) - ); - const [dirtyList, setDirtyList] = useState(() => - inputScheme.map(() => false) - ); - const [inputError, setInputError] = useState(); - - const close = (): void => { - if (step.type !== "process") { - props.close(); - if (step.type === "success" && props.onSuccessAndClose) { - props.onSuccessAndClose(step.data); - } - } else { - console.log("Attempt to close modal when processing."); - } - }; - - const onConfirm = (): void => { - setStep({ type: "process" }); - props - .onProcess( - values.map((v, index) => { - if (inputScheme[index].type === "datetime" && v !== "") - return new Date(v as string).toISOString(); - else return v; - }) as unknown as MapOperationInputInfoValueTypeList - ) - .then( - (d) => { - setStep({ - type: "success", - data: d, - }); - }, - (e: unknown) => { - setStep({ - type: "failure", - data: e, - }); - } - ); - }; - - let body: React.ReactNode; - if (step.type === "input" || step.type === "process") { - const process = step.type === "process"; - - let inputPrompt = - typeof props.inputPrompt === "function" - ? props.inputPrompt() - : convertI18nText(props.inputPrompt, t); - inputPrompt =
{inputPrompt}
; - - const validate = (values: ValueType[]): boolean => { - const { inputValidator } = props; - if (inputValidator != null) { - const result = inputValidator( - values as unknown as MapOperationInputInfoValueTypeList - ); - setInputError(result); - return isNoError(result); - } - return true; - }; - - const updateValue = (index: number, newValue: ValueType): void => { - const oldValues = values; - const newValues = oldValues.slice(); - newValues[index] = newValue; - setValues(newValues); - if (dirtyList[index] === false) { - const newDirtyList = dirtyList.slice(); - newDirtyList[index] = true; - setDirtyList(newDirtyList); - } - validate(newValues); - }; - - const canProcess = isNoError(inputError); - - body = ( - <> - - {inputPrompt} - {inputScheme.map((item, index) => { - const value = values[index]; - const error: string | null = - dirtyList[index] && inputError != null - ? convertI18nText(inputError[index], t) - : null; - - if (item.type === "text") { - return ( - - {item.label && ( - {convertI18nText(item.label, t)} - )} - { - const v = e.target.value; - updateValue(index, v); - }} - isInvalid={error != null} - disabled={process} - /> - {error != null && ( - - {error} - - )} - {item.helperText && ( - {t(item.helperText)} - )} - - ); - } else if (item.type === "bool") { - return ( - - - type="checkbox" - checked={value as boolean} - onChange={(event) => { - updateValue(index, event.currentTarget.checked); - }} - label={convertI18nText(item.label, t)} - disabled={process} - /> - - ); - } else if (item.type === "select") { - return ( - - {convertI18nText(item.label, t)} - { - updateValue(index, event.target.value); - }} - disabled={process} - > - {item.options.map((option, i) => { - return ( - - ); - })} - - - ); - } else if (item.type === "color") { - return ( - - {item.canBeNull ? ( - - type="checkbox" - checked={value !== null} - onChange={(event) => { - if (event.currentTarget.checked) { - updateValue(index, "#007bff"); - } else { - updateValue(index, null); - } - }} - label={convertI18nText(item.label, t)} - disabled={process} - /> - ) : ( - {convertI18nText(item.label, t)} - )} - {value !== null && ( - updateValue(index, result.hex)} - /> - )} - - ); - } else if (item.type === "datetime") { - return ( - - {item.label && ( - {convertI18nText(item.label, t)} - )} - { - const v = e.target.value; - updateValue(index, v); - }} - isInvalid={error != null} - disabled={process} - /> - {error != null && ( - - {error} - - )} - - ); - } - })} - - - - { - setDirtyList(inputScheme.map(() => true)); - if (validate(values)) { - onConfirm(); - } - }} - > - {t("operationDialog.confirm")} - - - - ); - } else { - let content: React.ReactNode; - const result = step; - if (result.type === "success") { - content = - props.successPrompt?.(result.data) ?? t("operationDialog.success"); - if (typeof content === "string") - content =

{content}

; - } else { - content = props.failurePrompt?.(result.data) ?? ; - if (typeof content === "string") - content = ; - } - body = ( - <> - {content} - - - - - ); - } - - const title = - typeof props.title === "function" - ? props.title() - : convertI18nText(props.title, t); - - return ( - - - {title} - - {body} - - ); -}; - -export default OperationDialog; diff --git a/FrontEnd/src/views/common/ToggleIconButton.tsx b/FrontEnd/src/views/common/ToggleIconButton.tsx deleted file mode 100644 index c4d2d132..00000000 --- a/FrontEnd/src/views/common/ToggleIconButton.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; -import classnames from "classnames"; - -export interface ToggleIconButtonProps - extends React.HTMLAttributes { - state: boolean; - trueIconClassName: string; - falseIconClassName: string; -} - -const ToggleIconButton: React.FC = ({ - state, - className, - trueIconClassName, - falseIconClassName, - ...otherProps -}) => { - return ( - - ); -}; - -export default ToggleIconButton; diff --git a/FrontEnd/src/views/common/button/LoadingButton.tsx b/FrontEnd/src/views/common/button/LoadingButton.tsx new file mode 100644 index 00000000..fd1c19b3 --- /dev/null +++ b/FrontEnd/src/views/common/button/LoadingButton.tsx @@ -0,0 +1,28 @@ +import React from "react"; + +const LoadingButton: React.FC<{ loading?: boolean } & ButtonProps> = ({ + loading, + variant, + disabled, + ...otherProps +}) => { + return ( + + ); +}; + +export default LoadingButton; diff --git a/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx b/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx new file mode 100644 index 00000000..1ad52350 --- /dev/null +++ b/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx @@ -0,0 +1,40 @@ +import { convertI18nText, I18nText } from "@/common"; +import React from "react"; +import { useTranslation } from "react-i18next"; + +import Button from "../button/Button"; +import Dialog from "./Dialog"; + +const ConfirmDialog: React.FC<{ + open?: boolean; + onClose: () => void; + onConfirm: () => void; + title: I18nText; + body: I18nText; +}> = ({ open, onClose, onConfirm, title, body }) => { + const { t } = useTranslation(); + + return ( + +

{convertI18nText(title, t)}

+

{convertI18nText(body, t)}

+
+
+
+ ); +}; + +export default ConfirmDialog; diff --git a/FrontEnd/src/views/common/dailog/Dialog.css b/FrontEnd/src/views/common/dailog/Dialog.css new file mode 100644 index 00000000..e69de29b diff --git a/FrontEnd/src/views/common/dailog/Dialog.tsx b/FrontEnd/src/views/common/dailog/Dialog.tsx new file mode 100644 index 00000000..5a3902c4 --- /dev/null +++ b/FrontEnd/src/views/common/dailog/Dialog.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +export interface DialogProps { + onClose: () => void; + open?: boolean; + children?: React.ReactNode; +} + +export default function Dialog(props: DialogProps): React.ReactElement | null { + const { open, onClose, children } = props; + + return
{children}
; +} diff --git a/FrontEnd/src/views/common/dailog/OperationDialog.tsx b/FrontEnd/src/views/common/dailog/OperationDialog.tsx new file mode 100644 index 00000000..129e85d5 --- /dev/null +++ b/FrontEnd/src/views/common/dailog/OperationDialog.tsx @@ -0,0 +1,472 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { TwitterPicker } from "react-color"; +import moment from "moment"; + +import { convertI18nText, I18nText, UiLogicError } from "@/common"; + +import Button from "../button/Button"; +import LoadingButton from "../button/LoadingButton"; +import Dialog from "./Dialog"; + +interface DefaultErrorPromptProps { + error?: string; +} + +const DefaultErrorPrompt: React.FC = (props) => { + const { t } = useTranslation(); + + let result =

{t("operationDialog.error")}

; + + if (props.error != null) { + result = ( + <> + {result} +

{props.error}

+ + ); + } + + return result; +}; + +export interface OperationDialogTextInput { + type: "text"; + label?: I18nText; + password?: boolean; + initValue?: string; + textFieldProps?: Omit< + React.InputHTMLAttributes, + "type" | "value" | "onChange" | "aria-relevant" + >; + helperText?: string; +} + +export interface OperationDialogBoolInput { + type: "bool"; + label: I18nText; + initValue?: boolean; +} + +export interface OperationDialogSelectInputOption { + value: string; + label: I18nText; + icon?: React.ReactElement; +} + +export interface OperationDialogSelectInput { + type: "select"; + label: I18nText; + options: OperationDialogSelectInputOption[]; + initValue?: string; +} + +export interface OperationDialogColorInput { + type: "color"; + label?: I18nText; + initValue?: string | null; + canBeNull?: boolean; +} + +export interface OperationDialogDateTimeInput { + type: "datetime"; + label?: I18nText; + initValue?: string; +} + +export type OperationDialogInput = + | OperationDialogTextInput + | OperationDialogBoolInput + | OperationDialogSelectInput + | OperationDialogColorInput + | OperationDialogDateTimeInput; + +interface OperationInputTypeStringToValueTypeMap { + text: string; + bool: boolean; + select: string; + color: string | null; + datetime: string; +} + +type MapOperationInputTypeStringToValueType = + Type extends keyof OperationInputTypeStringToValueTypeMap + ? OperationInputTypeStringToValueTypeMap[Type] + : never; + +type MapOperationInputInfoValueType = T extends OperationDialogInput + ? MapOperationInputTypeStringToValueType + : T; + +const initValueMapperMap: { + [T in OperationDialogInput as T["type"]]: ( + item: T + ) => MapOperationInputInfoValueType; +} = { + bool: (item) => item.initValue ?? false, + color: (item) => item.initValue ?? null, + datetime: (item) => { + if (item.initValue != null) { + return moment(item.initValue).format("YYYY-MM-DDTHH:mm:ss"); + } else { + return ""; + } + }, + select: (item) => item.initValue ?? item.options[0].value, + text: (item) => item.initValue ?? "", +}; + +type MapOperationInputInfoValueTypeList< + Tuple extends readonly OperationDialogInput[] +> = { + [Index in keyof Tuple]: MapOperationInputInfoValueType; +} & { length: Tuple["length"] }; + +export type OperationInputError = + | { + [index: number]: I18nText | null | undefined; + } + | null + | undefined; + +const isNoError = (error: OperationInputError): boolean => { + if (error == null) return true; + for (const key in error) { + if (error[key] != null) return false; + } + return true; +}; + +export interface OperationDialogProps< + TData, + OperationInputInfoList extends readonly OperationDialogInput[] +> { + open: boolean; + onClose: () => void; + title: I18nText | (() => React.ReactNode); + themeColor?: "danger" | "success" | string; + onProcess: ( + inputs: MapOperationInputInfoValueTypeList + ) => Promise; + inputScheme?: OperationInputInfoList; + inputValidator?: ( + inputs: MapOperationInputInfoValueTypeList + ) => OperationInputError; + inputPrompt?: I18nText | (() => React.ReactNode); + processPrompt?: () => React.ReactNode; + successPrompt?: (data: TData) => React.ReactNode; + failurePrompt?: (error: unknown) => React.ReactNode; + onSuccessAndClose?: (data: TData) => void; +} + +const OperationDialog = < + TData, + OperationInputInfoList extends readonly OperationDialogInput[] +>( + props: OperationDialogProps +): React.ReactElement => { + const inputScheme = (props.inputScheme ?? + []) as readonly OperationDialogInput[]; + + const { t } = useTranslation(); + + type Step = + | { type: "input" } + | { type: "process" } + | { + type: "success"; + data: TData; + } + | { + type: "failure"; + data: unknown; + }; + const [step, setStep] = useState({ type: "input" }); + + type ValueType = boolean | string | null | undefined; + + const [values, setValues] = useState( + inputScheme.map((item) => { + if (item.type in initValueMapperMap) { + return ( + initValueMapperMap[item.type] as ( + i: OperationDialogInput + ) => ValueType + )(item); + } else { + throw new UiLogicError("Unknown input scheme."); + } + }) + ); + const [dirtyList, setDirtyList] = useState(() => + inputScheme.map(() => false) + ); + const [inputError, setInputError] = useState(); + + const close = (): void => { + if (step.type !== "process") { + props.onClose(); + if (step.type === "success" && props.onSuccessAndClose) { + props.onSuccessAndClose(step.data); + } + } else { + console.log("Attempt to close modal when processing."); + } + }; + + const onConfirm = (): void => { + setStep({ type: "process" }); + props + .onProcess( + values.map((v, index) => { + if (inputScheme[index].type === "datetime" && v !== "") + return new Date(v as string).toISOString(); + else return v; + }) as unknown as MapOperationInputInfoValueTypeList + ) + .then( + (d) => { + setStep({ + type: "success", + data: d, + }); + }, + (e: unknown) => { + setStep({ + type: "failure", + data: e, + }); + } + ); + }; + + let body: React.ReactNode; + if (step.type === "input" || step.type === "process") { + const process = step.type === "process"; + + let inputPrompt = + typeof props.inputPrompt === "function" + ? props.inputPrompt() + : convertI18nText(props.inputPrompt, t); + inputPrompt =
{inputPrompt}
; + + const validate = (values: ValueType[]): boolean => { + const { inputValidator } = props; + if (inputValidator != null) { + const result = inputValidator( + values as unknown as MapOperationInputInfoValueTypeList + ); + setInputError(result); + return isNoError(result); + } + return true; + }; + + const updateValue = (index: number, newValue: ValueType): void => { + const oldValues = values; + const newValues = oldValues.slice(); + newValues[index] = newValue; + setValues(newValues); + if (dirtyList[index] === false) { + const newDirtyList = dirtyList.slice(); + newDirtyList[index] = true; + setDirtyList(newDirtyList); + } + validate(newValues); + }; + + const canProcess = isNoError(inputError); + + body = ( + <> +
+ {inputPrompt} + {inputScheme.map((item, index) => { + const value = values[index]; + const error: string | null = + dirtyList[index] && inputError != null + ? convertI18nText(inputError[index], t) + : null; + + if (item.type === "text") { + return ( + + {item.label && ( + {convertI18nText(item.label, t)} + )} + { + const v = e.target.value; + updateValue(index, v); + }} + isInvalid={error != null} + disabled={process} + /> + {error != null && ( + + {error} + + )} + {item.helperText && ( + {t(item.helperText)} + )} + + ); + } else if (item.type === "bool") { + return ( + + + type="checkbox" + checked={value as boolean} + onChange={(event) => { + updateValue(index, event.currentTarget.checked); + }} + label={convertI18nText(item.label, t)} + disabled={process} + /> + + ); + } else if (item.type === "select") { + return ( + + {convertI18nText(item.label, t)} + { + updateValue(index, event.target.value); + }} + disabled={process} + > + {item.options.map((option, i) => { + return ( + + ); + })} + + + ); + } else if (item.type === "color") { + return ( + + {item.canBeNull ? ( + + type="checkbox" + checked={value !== null} + onChange={(event) => { + if (event.currentTarget.checked) { + updateValue(index, "#007bff"); + } else { + updateValue(index, null); + } + }} + label={convertI18nText(item.label, t)} + disabled={process} + /> + ) : ( + {convertI18nText(item.label, t)} + )} + {value !== null && ( + updateValue(index, result.hex)} + /> + )} + + ); + } else if (item.type === "datetime") { + return ( + + {item.label && ( + {convertI18nText(item.label, t)} + )} + { + const v = e.target.value; + updateValue(index, v); + }} + isInvalid={error != null} + disabled={process} + /> + {error != null && ( + + {error} + + )} + + ); + } + })} +
+
+
+ + ); + } else { + let content: React.ReactNode; + const result = step; + if (result.type === "success") { + content = + props.successPrompt?.(result.data) ?? t("operationDialog.success"); + if (typeof content === "string") + content =

{content}

; + } else { + content = props.failurePrompt?.(result.data) ?? ; + if (typeof content === "string") + content = ; + } + body = ( + <> +
{content}
+
+
+ + ); + } + + const title = + typeof props.title === "function" + ? props.title() + : convertI18nText(props.title, t); + + return ( + +

+ {title} +

+ {body} +
+ ); +}; + +export default OperationDialog; diff --git a/FrontEnd/src/views/login/index.tsx b/FrontEnd/src/views/login/index.tsx index 55bd2f8c..89aeb47a 100644 --- a/FrontEnd/src/views/login/index.tsx +++ b/FrontEnd/src/views/login/index.tsx @@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next"; import { useUser, userService } from "@/services/user"; import AppBar from "../common/AppBar"; -import LoadingButton from "../common/LoadingButton"; +import LoadingButton from "../common/button/LoadingButton"; import "./index.css"; diff --git a/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx b/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx index 1baab1cc..0bf51c21 100644 --- a/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx +++ b/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx @@ -10,6 +10,7 @@ import { getHttpUserClient } from "@/http/user"; import ImageCropper, { Clip, applyClipToImage } from "../common/ImageCropper"; import Button from "../common/button/Button"; +import Dialog from "../common/dailog/Dialog"; export interface ChangeAvatarDialogProps { open: boolean; @@ -159,29 +160,27 @@ const ChangeAvatarDialog: React.FC = (props) => { }; return ( - - - {t("settings.dialogChangeAvatar.title")} - + +

{t("settings.dialogChangeAvatar.title")}

{(() => { if (state === "select") { return ( <> - +
{t("settings.dialogChangeAvatar.prompt.select")}
- - +
+
); } else if (state === "crop") { @@ -190,7 +189,7 @@ const ChangeAvatarDialog: React.FC = (props) => { } return ( <> - +
= (props) => {
{t("settings.dialogChangeAvatar.prompt.crop")}
- - +
+
); } else if (state === "processcrop") { return ( <> - +
{t("settings.dialogChangeAvatar.prompt.processingCrop")}
- - +
+
); } else if (state === "preview") { return ( <> - +
{createPreviewRow()}
{t("settings.dialogChangeAvatar.prompt.preview")}
- - +
+
); } else if (state === "uploading") { return ( <> - +
{createPreviewRow()}
{t("settings.dialogChangeAvatar.prompt.uploading")}
- - +
+
); } else if (state === "success") { return ( <> - +
{t("operationDialog.success")}
- - +
+
); } else { return ( <> - +
{createPreviewRow()}
{trueMessage}
- - +
+
); } })()} - +
); }; diff --git a/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx b/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx index 4b44cdd6..605796ca 100644 --- a/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx +++ b/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx @@ -2,7 +2,7 @@ import { getHttpUserClient } from "@/http/user"; import { useUserLoggedIn } from "@/services/user"; import React from "react"; -import OperationDialog from "../common/OperationDialog"; +import OperationDialog from "../common/dailog/OperationDialog"; export interface ChangeNicknameDialogProps { open: boolean; @@ -24,7 +24,7 @@ const ChangeNicknameDialog: React.FC = (props) => { nickname: newNickname, }); }} - close={props.close} + onClose={props.close} /> ); }; diff --git a/FrontEnd/src/views/settings/ChangePasswordDialog.tsx b/FrontEnd/src/views/settings/ChangePasswordDialog.tsx index 21eeeb09..944fdaed 100644 --- a/FrontEnd/src/views/settings/ChangePasswordDialog.tsx +++ b/FrontEnd/src/views/settings/ChangePasswordDialog.tsx @@ -3,7 +3,7 @@ import { useHistory } from "react-router"; import { userService } from "@/services/user"; -import OperationDialog from "../common/OperationDialog"; +import OperationDialog from "../common/dailog/OperationDialog"; export interface ChangePasswordDialogProps { open: boolean; @@ -55,7 +55,7 @@ const ChangePasswordDialog: React.FC = (props) => { await userService.changePassword(oldPassword, newPassword); setRedirect(true); }} - close={() => { + onClose={() => { props.close(); if (redirect) { history.push("/login"); diff --git a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx index 005da933..0e43cb6e 100644 --- a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx +++ b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx @@ -9,7 +9,7 @@ import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; import FlatButton from "../common/button/FlatButton"; import TabPages from "../common/TabPages"; import TimelinePostBuilder from "@/services/TimelinePostBuilder"; -import ConfirmDialog from "../common/ConfirmDialog"; +import ConfirmDialog from "../common/dailog/ConfirmDialog"; export interface MarkdownPostEditProps { timeline: string; diff --git a/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx b/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx index 001e52d7..988124b6 100644 --- a/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx +++ b/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx @@ -2,7 +2,7 @@ import React from "react"; import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; -import OperationDialog from "../common/OperationDialog"; +import OperationDialog from "../common/dailog/OperationDialog"; function PostPropertyChangeDialog(props: { onClose: () => void; @@ -14,7 +14,7 @@ function PostPropertyChangeDialog(props: { return ( ] as const } open={props.open} - close={props.close} + onClose={props.close} onProcess={([newTitle, newVisibility, newDescription, newColor]) => { const req: HttpTimelinePatchRequest = {}; if (newTitle !== timeline.title) { diff --git a/FrontEnd/src/views/timeline/TimelineDeleteDialog.tsx b/FrontEnd/src/views/timeline/TimelineDeleteDialog.tsx index dbca62ca..aedf4f29 100644 --- a/FrontEnd/src/views/timeline/TimelineDeleteDialog.tsx +++ b/FrontEnd/src/views/timeline/TimelineDeleteDialog.tsx @@ -4,7 +4,7 @@ import { Trans } from "react-i18next"; import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; -import OperationDialog from "../common/OperationDialog"; +import OperationDialog from "../common/dailog/OperationDialog"; interface TimelineDeleteDialog { timeline: HttpTimelineInfo; @@ -20,7 +20,7 @@ const TimelineDeleteDialog: React.FC = (props) => { return ( { -- cgit v1.2.3 From a168336c0761b263ee5371218cbf6da236c0acce Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 26 Jun 2021 19:13:19 +0800 Subject: ... --- FrontEnd/src/views/admin/UserAdmin.tsx | 9 +- FrontEnd/src/views/common/Menu.tsx | 26 ++--- FrontEnd/src/views/common/SearchInput.tsx | 13 +-- FrontEnd/src/views/common/Spinner.tsx | 13 +++ FrontEnd/src/views/common/TabPages.tsx | 20 +--- FrontEnd/src/views/common/alert/AlertHost.tsx | 11 +- FrontEnd/src/views/common/button/LoadingButton.tsx | 23 ++--- .../src/views/common/dailog/OperationDialog.tsx | 71 ++++++------- FrontEnd/src/views/login/index.tsx | 114 ++++++++++----------- .../src/views/timeline-common/MarkdownPostEdit.tsx | 8 +- .../src/views/timeline-common/TimelineMember.tsx | 21 ++-- .../views/timeline-common/TimelinePageTemplate.tsx | 1 - .../TimelinePostDeleteConfirmDialog.tsx | 40 -------- .../src/views/timeline-common/TimelinePostEdit.tsx | 1 - .../src/views/timeline-common/TimelinePostView.tsx | 6 +- 15 files changed, 142 insertions(+), 235 deletions(-) create mode 100644 FrontEnd/src/views/common/Spinner.tsx delete mode 100644 FrontEnd/src/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx (limited to 'FrontEnd/src/views/common/button') diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx index 125219c3..481db1cc 100644 --- a/FrontEnd/src/views/admin/UserAdmin.tsx +++ b/FrontEnd/src/views/admin/UserAdmin.tsx @@ -15,6 +15,7 @@ import { import { Trans, useTranslation } from "react-i18next"; import Button from "../common/button/Button"; import TextButton from "../common/button/TextButton"; +import Spinner from "../common/Spinner"; interface DialogProps { open: boolean; @@ -203,7 +204,7 @@ const UserItem: React.FC = ({ user, on }) => { const [editMaskVisible, setEditMaskVisible] = React.useState(false); return ( - +
setEditMaskVisible(true)} @@ -242,7 +243,7 @@ const UserItem: React.FC = ({ user, on }) => { onClick={on[kDelete]} />
-
+ ); }; @@ -251,8 +252,6 @@ interface UserAdminProps { } const UserAdmin: React.FC = () => { - const { t } = useTranslation(); - type DialogInfo = | null | { @@ -390,7 +389,7 @@ const UserAdmin: React.FC = () => { ); } else { - return ; + return ; } }; diff --git a/FrontEnd/src/views/common/Menu.tsx b/FrontEnd/src/views/common/Menu.tsx index ae73a331..a5d2ec2c 100644 --- a/FrontEnd/src/views/common/Menu.tsx +++ b/FrontEnd/src/views/common/Menu.tsx @@ -1,9 +1,9 @@ import React from "react"; import classnames from "classnames"; -import { OverlayTrigger, OverlayTriggerProps, Popover } from "react-bootstrap"; import { useTranslation } from "react-i18next"; -import { BootstrapThemeColor, convertI18nText, I18nText } from "@/common"; +import { convertI18nText, I18nText } from "@/common"; +import { PaletteColorType } from "@/palette"; export type MenuItem = | { @@ -13,7 +13,7 @@ export type MenuItem = type: "button"; text: I18nText; iconClassName?: string; - color?: BootstrapThemeColor; + color?: PaletteColorType; onClick: () => void; }; @@ -67,26 +67,14 @@ export default Menu; export interface PopupMenuProps { items: MenuItems; - children: OverlayTriggerProps["children"]; + children: React.ReactElement; } export const PopupMenu: React.FC = ({ items, children }) => { const [show, setShow] = React.useState(false); const toggle = (): void => setShow(!show); - return ( - - setShow(false)} /> - - } - show={show} - onToggle={toggle} - > - {children} - - ); + // TODO: + + return setShow(false)} />; }; diff --git a/FrontEnd/src/views/common/SearchInput.tsx b/FrontEnd/src/views/common/SearchInput.tsx index 79eb2732..fff11b95 100644 --- a/FrontEnd/src/views/common/SearchInput.tsx +++ b/FrontEnd/src/views/common/SearchInput.tsx @@ -1,7 +1,8 @@ import React, { useCallback } from "react"; import classnames from "classnames"; import { useTranslation } from "react-i18next"; -import { Spinner, Form, Button } from "react-bootstrap"; + +import LoadingButton from "./button/LoadingButton"; export interface SearchInputProps { value: string; @@ -64,13 +65,9 @@ const SearchInput: React.FC = (props) => { "flex-shrink-0" )} > - {props.loading ? ( - - ) : ( - - )} + + {props.buttonText ?? t("search")} + ); diff --git a/FrontEnd/src/views/common/Spinner.tsx b/FrontEnd/src/views/common/Spinner.tsx new file mode 100644 index 00000000..783c9be2 --- /dev/null +++ b/FrontEnd/src/views/common/Spinner.tsx @@ -0,0 +1,13 @@ +import { PaletteColorType } from "@/palette"; +import React from "react"; + +export interface SpinnerProps { + size?: "sm" | "md" | "lg" | number; + color?: PaletteColorType; +} + +export default function Spinner( + props: SpinnerProps +): React.ReactElement | null { + return ; +} diff --git a/FrontEnd/src/views/common/TabPages.tsx b/FrontEnd/src/views/common/TabPages.tsx index 2b1d91cb..b7a9fb36 100644 --- a/FrontEnd/src/views/common/TabPages.tsx +++ b/FrontEnd/src/views/common/TabPages.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { Nav } from "react-bootstrap"; import { useTranslation } from "react-i18next"; import { convertI18nText, I18nText, UiLogicError } from "@/common"; @@ -31,6 +30,8 @@ const TabPages: React.FC = ({ pageContainerClassName, pageContainerStyle, }) => { + // TODO: + if (pages.length === 0) { throw new UiLogicError("Page list can't be empty."); } @@ -47,23 +48,6 @@ const TabPages: React.FC = ({ return (
-
{currentPage.page}
diff --git a/FrontEnd/src/views/common/alert/AlertHost.tsx b/FrontEnd/src/views/common/alert/AlertHost.tsx index 949be7ed..21b9882d 100644 --- a/FrontEnd/src/views/common/alert/AlertHost.tsx +++ b/FrontEnd/src/views/common/alert/AlertHost.tsx @@ -1,7 +1,6 @@ import React from "react"; import without from "lodash/without"; import { useTranslation } from "react-i18next"; -import { Alert } from "react-bootstrap"; import { alertService, @@ -52,13 +51,7 @@ export const AutoCloseAlert: React.FC = (props) => { }; return ( - +
{(() => { const { message } = alert; if (typeof message === "function") { @@ -66,7 +59,7 @@ export const AutoCloseAlert: React.FC = (props) => { return ; } else return convertI18nText(message, t); })()} - +
); }; diff --git a/FrontEnd/src/views/common/button/LoadingButton.tsx b/FrontEnd/src/views/common/button/LoadingButton.tsx index fd1c19b3..aee83aa2 100644 --- a/FrontEnd/src/views/common/button/LoadingButton.tsx +++ b/FrontEnd/src/views/common/button/LoadingButton.tsx @@ -1,26 +1,19 @@ import React from "react"; -const LoadingButton: React.FC<{ loading?: boolean } & ButtonProps> = ({ +import { CommonButtonProps } from "./common"; +import Button from "./Button"; +import Spinner from "../Spinner"; + +const LoadingButton: React.FC<{ loading?: boolean } & CommonButtonProps> = ({ loading, - variant, disabled, + color, ...otherProps }) => { return ( - ); }; diff --git a/FrontEnd/src/views/common/dailog/OperationDialog.tsx b/FrontEnd/src/views/common/dailog/OperationDialog.tsx index 129e85d5..1e765276 100644 --- a/FrontEnd/src/views/common/dailog/OperationDialog.tsx +++ b/FrontEnd/src/views/common/dailog/OperationDialog.tsx @@ -5,6 +5,8 @@ import moment from "moment"; import { convertI18nText, I18nText, UiLogicError } from "@/common"; +import { PaletteColorType } from "@/palette"; + import Button from "../button/Button"; import LoadingButton from "../button/LoadingButton"; import Dialog from "./Dialog"; @@ -144,7 +146,7 @@ export interface OperationDialogProps< open: boolean; onClose: () => void; title: I18nText | (() => React.ReactNode); - themeColor?: "danger" | "success" | string; + themeColor?: PaletteColorType; onProcess: ( inputs: MapOperationInputInfoValueTypeList ) => Promise; @@ -290,50 +292,42 @@ const OperationDialog = < if (item.type === "text") { return ( - +
{item.label && ( - {convertI18nText(item.label, t)} + )} - { const v = e.target.value; updateValue(index, v); }} - isInvalid={error != null} disabled={process} /> - {error != null && ( - - {error} - - )} - {item.helperText && ( - {t(item.helperText)} - )} - + {error != null &&
{error}
} + {item.helperText &&
{t(item.helperText)}
} +
); } else if (item.type === "bool") { return ( - - +
+ { updateValue(index, event.currentTarget.checked); }} - label={convertI18nText(item.label, t)} disabled={process} /> - + +
); } else if (item.type === "select") { return ( - - {convertI18nText(item.label, t)} - + + +
); } else if (item.type === "color") { return ( - +
{item.canBeNull ? ( - + { @@ -365,42 +359,35 @@ const OperationDialog = < updateValue(index, null); } }} - label={convertI18nText(item.label, t)} disabled={process} /> - ) : ( - {convertI18nText(item.label, t)} - )} + ) : null} + {value !== null && ( updateValue(index, result.hex)} /> )} - +
); } else if (item.type === "datetime") { return ( - +
{item.label && ( - {convertI18nText(item.label, t)} + )} - { const v = e.target.value; updateValue(index, v); }} - isInvalid={error != null} disabled={process} /> - {error != null && ( - - {error} - - )} - + {error != null &&
{error}
} +
); } })} @@ -412,7 +399,7 @@ const OperationDialog = < onClick={close} /> { diff --git a/FrontEnd/src/views/login/index.tsx b/FrontEnd/src/views/login/index.tsx index 6c0aaf67..6d70c64a 100644 --- a/FrontEnd/src/views/login/index.tsx +++ b/FrontEnd/src/views/login/index.tsx @@ -81,67 +81,59 @@ const LoginPage: React.FC = (_) => {

{t("welcome")}

- {t("user.username")} - { - setUsername(e.target.value); - setUsernameDirty(true); - }} - value={username} - isInvalid={usernameDirty && username === ""} - /> - {usernameDirty && username === "" && ( - - {t("login.emptyUsername")} - - )} - {t("user.password")} - { - setPassword(e.target.value); - setPasswordDirty(true); - }} - value={password} - onKeyDown={onEnterPressInPassword} - isInvalid={passwordDirty && password === ""} - /> - {passwordDirty && password === "" && ( - - {t("login.emptyPassword")} - - )} - - - - id="remember-me" - type="checkbox" - checked={rememberMe} - onChange={(e) => { - setRememberMe(e.currentTarget.checked); - }} - label={t("user.rememberMe")} - /> - - {error ?

{t(error)}

: null} -
- { - submit(); - e.preventDefault(); - }} - disabled={username === "" || password === "" ? true : undefined} - > - {t("user.login")} - -
- + + { + setUsername(e.target.value); + setUsernameDirty(true); + }} + value={username} + /> + {usernameDirty && username === "" && ( +
{t("login.emptyUsername")}
+ )} + + { + setPassword(e.target.value); + setPasswordDirty(true); + }} + value={password} + onKeyDown={onEnterPressInPassword} + /> + {passwordDirty && password === "" && ( +
{t("login.emptyPassword")}
+ )} +
+
+ { + setRememberMe(e.currentTarget.checked); + }} + /> + +
+ {error ?

{t(error)}

: null} +
+ { + submit(); + e.preventDefault(); + }} + disabled={username === "" || password === "" ? true : undefined} + > + {t("user.login")} + +
); }; diff --git a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx index 6cb64dd3..9f0753b9 100644 --- a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx +++ b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx @@ -1,15 +1,17 @@ +/* eslint-disable react/jsx-no-undef */ import React from "react"; import classnames from "classnames"; -import { Form, Spinner } from "react-bootstrap"; import { useTranslation } from "react-i18next"; import { Prompt } from "react-router"; import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; +import TimelinePostBuilder from "@/services/TimelinePostBuilder"; + import FlatButton from "../common/button/FlatButton"; import TabPages from "../common/TabPages"; -import TimelinePostBuilder from "@/services/TimelinePostBuilder"; import ConfirmDialog from "../common/dailog/ConfirmDialog"; +import Spinner from "../common/Spinner"; export interface MarkdownPostEditProps { timeline: string; @@ -102,7 +104,7 @@ const MarkdownPostEdit: React.FC = ({ pageContainerClassName="py-2" actions={ process ? ( - + ) : ( <> void; }> = ({ user, add, onAction }) => { - const { t } = useTranslation(); - return ( - +
@@ -42,7 +41,7 @@ const TimelineMemberItem: React.FC<{
) : null}
- +
); }; @@ -109,7 +108,7 @@ const TimelineMemberUserSearch: React.FC<{ return
{t("timeline.member.noUserAvailableToAdd")}
; } else { return ( - +
{users.map((user) => ( ))} - +
); } } else if (userSearchState.type === "error") { @@ -152,7 +151,7 @@ const TimelineMember: React.FC = (props) => { return (
- +
{members.map((member, index) => ( = (props) => { } /> ))} - +
{timeline.manageable ? ( ) : null} @@ -187,8 +186,8 @@ export const TimelineMemberDialog: React.FC = ( props ) => { return ( - + - + ); }; diff --git a/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx index d05f18d4..ea6e8d40 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { Container } from "react-bootstrap"; import { HubConnectionState } from "@microsoft/signalr"; import { HttpTimelineInfo } from "@/http/timeline"; diff --git a/FrontEnd/src/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx b/FrontEnd/src/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx deleted file mode 100644 index e04bb7e1..00000000 --- a/FrontEnd/src/views/timeline-common/TimelinePostDeleteConfirmDialog.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; - -import Button from "../common/button/Button"; - -const TimelinePostDeleteConfirmDialog: React.FC<{ - onClose: () => void; - onConfirm: () => void; -}> = ({ onClose, onConfirm }) => { - const { t } = useTranslation(); - - return ( - - - - {t("timeline.post.deleteDialog.title")} - - - {t("timeline.post.deleteDialog.prompt")} - - - - - ); -}; - -export default TimelinePostDeleteConfirmDialog; diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx index 5e59bee4..13aacb54 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx @@ -265,7 +265,6 @@ const TimelinePostEdit: React.FC = (props) => {
= (props) => { ) : null} {dialog === "delete" ? ( - { setDialog(null); setOperationMaskVisible(false); -- cgit v1.2.3 From c3f17f1dd1099c244e36d09b14c3e131d703830e Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 26 Jun 2021 19:54:24 +0800 Subject: ... --- FrontEnd/index.html | 1 + FrontEnd/src/index.css | 109 ++++++++++++++++----- FrontEnd/src/index.tsx | 4 - FrontEnd/src/palette.ts | 51 ++++++++-- FrontEnd/src/views/admin/AdminNav.tsx | 50 ++++------ FrontEnd/src/views/center/TimelineBoard.tsx | 4 +- FrontEnd/src/views/center/index.tsx | 2 +- FrontEnd/src/views/common/AppBar.css | 90 +++++++++++++++++ FrontEnd/src/views/common/AppBar.tsx | 2 +- FrontEnd/src/views/common/FullPage.tsx | 39 -------- FrontEnd/src/views/common/LoadingPage.tsx | 5 +- FrontEnd/src/views/common/SearchInput.css | 4 + FrontEnd/src/views/common/SearchInput.tsx | 2 + FrontEnd/src/views/common/TabPages.tsx | 58 ----------- FrontEnd/src/views/common/alert/alert.css | 4 + FrontEnd/src/views/common/alert/alert.sass | 15 --- FrontEnd/src/views/common/button/Button.css | 72 ++++++++++++++ FrontEnd/src/views/common/button/Button.tsx | 9 +- FrontEnd/src/views/common/button/FlatButton.css | 8 +- FrontEnd/src/views/common/button/TextButton.css | 8 +- .../src/views/common/dailog/FullPageDialog.tsx | 39 ++++++++ FrontEnd/src/views/common/index.css | 95 +----------------- FrontEnd/src/views/common/tab/TabPages.tsx | 58 +++++++++++ FrontEnd/src/views/common/tab/Tabs.tsx | 19 ++++ .../src/views/timeline-common/MarkdownPostEdit.tsx | 2 +- .../timeline-common/TimelinePageCardTemplate.tsx | 6 +- FrontEnd/src/views/timeline-common/index.css | 6 +- 27 files changed, 465 insertions(+), 297 deletions(-) create mode 100644 FrontEnd/src/views/common/AppBar.css delete mode 100644 FrontEnd/src/views/common/FullPage.tsx create mode 100644 FrontEnd/src/views/common/SearchInput.css delete mode 100644 FrontEnd/src/views/common/TabPages.tsx create mode 100644 FrontEnd/src/views/common/alert/alert.css delete mode 100644 FrontEnd/src/views/common/alert/alert.sass create mode 100644 FrontEnd/src/views/common/dailog/FullPageDialog.tsx create mode 100644 FrontEnd/src/views/common/tab/TabPages.tsx create mode 100644 FrontEnd/src/views/common/tab/Tabs.tsx (limited to 'FrontEnd/src/views/common/button') diff --git a/FrontEnd/index.html b/FrontEnd/index.html index 87e19743..ccd6a21b 100644 --- a/FrontEnd/index.html +++ b/FrontEnd/index.html @@ -24,6 +24,7 @@
+
diff --git a/FrontEnd/src/index.css b/FrontEnd/src/index.css index 3ca9f10c..d4efabed 100644 --- a/FrontEnd/src/index.css +++ b/FrontEnd/src/index.css @@ -1,36 +1,101 @@ -@import url("./bootstrap-grid.css"); +@import "bootstrap/dist/css/bootstrap-reboot.css"; +@import "bootstrap/dist/css/bootstrap-grid.css"; +@import "bootstrap-icons/font/bootstrap-icons.css"; :root { --tl-background-color: #f8f9fa; --tl-primary-color: rgb(0, 123, 255); - --tl-primary-inactive-color: rgb(26, 136, 255); - --tl-primary-lighter-color: rgb(26, 136, 255); - --tl-primary-darker-color: rgb(0, 111, 230); + --tl-primary-l1-color: rgb(26, 136, 255); + --tl-primary-l2-color: rgb(51, 149, 255); + --tl-primary-l3-color: rgb(77, 163, 255); + --tl-primary-d1-color: rgb(0, 111, 230); + --tl-primary-d2-color: rgb(0, 98, 204); + --tl-primary-d3-color: rgb(0, 86, 179); + --tl-primary-f1-color: rgb(0, 111, 230); + --tl-primary-f2-color: rgb(0, 98, 204); + --tl-primary-f3-color: rgb(0, 86, 179); + --tl-primary-r1-color: rgb(26, 136, 255); + --tl-primary-r2-color: rgb(51, 149, 255); + --tl-primary-r3-color: rgb(77, 163, 255); --tl-primary-enhance-color: rgb(77, 163, 255); - --tl-primary-enhance-inactive-color: rgb(43, 145, 255); - --tl-primary-enhance-lighter-color: rgb(94, 172, 255); - --tl-primary-enhance-darker-color: rgb(43, 145, 255); + --tl-primary-enhance-l1-color: rgb(94, 172, 255); + --tl-primary-enhance-l2-color: rgb(112, 181, 255); + --tl-primary-enhance-l3-color: rgb(130, 190, 255); + --tl-primary-enhance-d1-color: rgb(43, 145, 255); + --tl-primary-enhance-d2-color: rgb(10, 128, 255); + --tl-primary-enhance-d3-color: rgb(0, 112, 232); + --tl-primary-enhance-f1-color: rgb(94, 172, 255); + --tl-primary-enhance-f2-color: rgb(112, 181, 255); + --tl-primary-enhance-f3-color: rgb(130, 190, 255); + --tl-primary-enhance-r1-color: rgb(43, 145, 255); + --tl-primary-enhance-r2-color: rgb(10, 128, 255); + --tl-primary-enhance-r3-color: rgb(0, 112, 232); --tl-secondary-color: rgb(255, 0, 250); - --tl-secondary-inactive-color: rgb(255, 26, 251); - --tl-secondary-lighter-color: rgb(255, 26, 251); - --tl-secondary-darker-color: rgb(230, 0, 225); + --tl-secondary-l1-color: rgb(255, 26, 251); + --tl-secondary-l2-color: rgb(255, 51, 251); + --tl-secondary-l3-color: rgb(255, 77, 252); + --tl-secondary-d1-color: rgb(230, 0, 225); + --tl-secondary-d2-color: rgb(204, 0, 200); + --tl-secondary-d3-color: rgb(179, 0, 175); + --tl-secondary-f1-color: rgb(230, 0, 225); + --tl-secondary-f2-color: rgb(204, 0, 200); + --tl-secondary-f3-color: rgb(179, 0, 175); + --tl-secondary-r1-color: rgb(255, 26, 251); + --tl-secondary-r2-color: rgb(255, 51, 251); + --tl-secondary-r3-color: rgb(255, 77, 252); --tl-text-primary-color: rgb(17, 17, 17); - --tl-text-primary-inactive-color: rgb(41, 41, 41); - --tl-text-primary-lighter-color: rgb(41, 41, 41); - --tl-text-primary-darker-color: rgb(15, 15, 15); + --tl-text-primary-l1-color: rgb(41, 41, 41); + --tl-text-primary-l2-color: rgb(65, 65, 65); + --tl-text-primary-l3-color: rgb(88, 88, 88); + --tl-text-primary-d1-color: rgb(15, 15, 15); + --tl-text-primary-d2-color: rgb(14, 14, 14); + --tl-text-primary-d3-color: rgb(12, 12, 12); + --tl-text-primary-f1-color: rgb(15, 15, 15); + --tl-text-primary-f2-color: rgb(14, 14, 14); + --tl-text-primary-f3-color: rgb(12, 12, 12); + --tl-text-primary-r1-color: rgb(41, 41, 41); + --tl-text-primary-r2-color: rgb(65, 65, 65); + --tl-text-primary-r3-color: rgb(88, 88, 88); --tl-text-on-primary-color: rgb(255, 255, 255); - --tl-text-on-primary-inactive-color: rgb(230, 230, 230); - --tl-text-on-primary-lighter-color: rgb(255, 255, 255); - --tl-text-on-primary-darker-color: rgb(230, 230, 230); + --tl-text-on-primary-l1-color: rgb(255, 255, 255); + --tl-text-on-primary-l2-color: rgb(255, 255, 255); + --tl-text-on-primary-l3-color: rgb(255, 255, 255); + --tl-text-on-primary-d1-color: rgb(230, 230, 230); + --tl-text-on-primary-d2-color: rgb(204, 204, 204); + --tl-text-on-primary-d3-color: rgb(179, 179, 179); + --tl-text-on-primary-f1-color: rgb(255, 255, 255); + --tl-text-on-primary-f2-color: rgb(255, 255, 255); + --tl-text-on-primary-f3-color: rgb(255, 255, 255); + --tl-text-on-primary-r1-color: rgb(230, 230, 230); + --tl-text-on-primary-r2-color: rgb(204, 204, 204); + --tl-text-on-primary-r3-color: rgb(179, 179, 179); --tl-danger-color: rgb(255, 0, 0); - --tl-danger-inactive-color: rgb(255, 26, 26); - --tl-danger-lighter-color: rgb(255, 26, 26); - --tl-danger-darker-color: rgb(230, 0, 0); + --tl-danger-l1-color: rgb(255, 26, 26); + --tl-danger-l2-color: rgb(255, 51, 51); + --tl-danger-l3-color: rgb(255, 77, 77); + --tl-danger-d1-color: rgb(230, 0, 0); + --tl-danger-d2-color: rgb(204, 0, 0); + --tl-danger-d3-color: rgb(179, 0, 0); + --tl-danger-f1-color: rgb(230, 0, 0); + --tl-danger-f2-color: rgb(204, 0, 0); + --tl-danger-f3-color: rgb(179, 0, 0); + --tl-danger-r1-color: rgb(255, 26, 26); + --tl-danger-r2-color: rgb(255, 51, 51); + --tl-danger-r3-color: rgb(255, 77, 77); --tl-success-color: rgb(0, 128, 0); - --tl-success-inactive-color: rgb(0, 166, 0); - --tl-success-lighter-color: rgb(0, 166, 0); - --tl-success-darker-color: rgb(0, 115, 0); + --tl-success-l1-color: rgb(0, 166, 0); + --tl-success-l2-color: rgb(0, 204, 0); + --tl-success-l3-color: rgb(0, 243, 0); + --tl-success-d1-color: rgb(0, 115, 0); + --tl-success-d2-color: rgb(0, 102, 0); + --tl-success-d3-color: rgb(0, 90, 0); + --tl-success-f1-color: rgb(0, 115, 0); + --tl-success-f2-color: rgb(0, 102, 0); + --tl-success-f3-color: rgb(0, 90, 0); + --tl-success-r1-color: rgb(0, 166, 0); + --tl-success-r2-color: rgb(0, 204, 0); + --tl-success-r3-color: rgb(0, 243, 0); } body { diff --git a/FrontEnd/src/index.tsx b/FrontEnd/src/index.tsx index 156fb2d9..e2132de0 100644 --- a/FrontEnd/src/index.tsx +++ b/FrontEnd/src/index.tsx @@ -3,10 +3,6 @@ import "core-js/modules/es.promise"; import "core-js/modules/es.array.iterator"; import "pepjs"; -import "bootstrap/dist/css/bootstrap-reboot.css"; -import "bootstrap/dist/css/bootstrap-grid.css"; -import "bootstrap-icons/font/bootstrap-icons.css"; - import React from "react"; import ReactDOM from "react-dom"; diff --git a/FrontEnd/src/palette.ts b/FrontEnd/src/palette.ts index ab3d6b54..fa99364f 100644 --- a/FrontEnd/src/palette.ts +++ b/FrontEnd/src/palette.ts @@ -15,9 +15,18 @@ function darkenBy(color: Color, ratio: number): Color { export interface PaletteColor { color: string; - lighter: string; - darker: string; - inactive: string; + l1: string; + l2: string; + l3: string; + d1: string; + d2: string; + d3: string; + f1: string; + f2: string; + f3: string; + r1: string; + r2: string; + r3: string; [key: string]: string; } @@ -37,13 +46,34 @@ export type Palette = Record; export function generatePaletteColor(color: string): PaletteColor { const c = Color(color); + const light = c.lightness() > 60; + const l1 = lightenBy(c, 0.1).rgb().toString(); + const l2 = lightenBy(c, 0.2).rgb().toString(); + const l3 = lightenBy(c, 0.3).rgb().toString(); + const d1 = darkenBy(c, 0.1).rgb().toString(); + const d2 = darkenBy(c, 0.2).rgb().toString(); + const d3 = darkenBy(c, 0.3).rgb().toString(); + const f1 = light ? l1 : d1; + const f2 = light ? l2 : d2; + const f3 = light ? l3 : d3; + const r1 = light ? d1 : l1; + const r2 = light ? d2 : l2; + const r3 = light ? d3 : l3; + return { color: c.rgb().toString(), - inactive: (c.lightness() > 60 ? darkenBy(c, 0.1) : lightenBy(c, 0.1)) - .rgb() - .toString(), - lighter: lightenBy(c, 0.1).rgb().toString(), - darker: darkenBy(c, 0.1).rgb().toString(), + l1, + l2, + l3, + d1, + d2, + d3, + f1, + f2, + f3, + r1, + r2, + r3, }; } @@ -91,7 +121,10 @@ export function generatePaletteCSS(palette: Palette): string { } const paletteSubject: BehaviorSubject = - new BehaviorSubject(null); + new BehaviorSubject( + // generatePalette({ primary: "rgb(0, 123, 255)" }) + null + ); export const palette$: Observable = paletteSubject.asObservable(); diff --git a/FrontEnd/src/views/admin/AdminNav.tsx b/FrontEnd/src/views/admin/AdminNav.tsx index 47e2138f..8b4c5fda 100644 --- a/FrontEnd/src/views/admin/AdminNav.tsx +++ b/FrontEnd/src/views/admin/AdminNav.tsx @@ -1,43 +1,29 @@ import React from "react"; -import { Nav } from "react-bootstrap"; -import { useTranslation } from "react-i18next"; -import { useHistory, useRouteMatch } from "react-router"; +import { useRouteMatch } from "react-router"; + +import Tabs from "../common/tab/Tabs"; const AdminNav: React.FC = () => { const match = useRouteMatch<{ name: string }>(); - const history = useHistory(); - - const { t } = useTranslation(); const name = match.params.name; - function toggle(newTab: string): void { - history.push(`/admin/${newTab}`); - } - return ( - + ); }; diff --git a/FrontEnd/src/views/center/TimelineBoard.tsx b/FrontEnd/src/views/center/TimelineBoard.tsx index d6f6228d..d7aa39ab 100644 --- a/FrontEnd/src/views/center/TimelineBoard.tsx +++ b/FrontEnd/src/views/center/TimelineBoard.tsx @@ -1,7 +1,6 @@ import React from "react"; import classnames from "classnames"; import { Link } from "react-router-dom"; -import { Spinner } from "react-bootstrap"; import { HttpTimelineInfo } from "@/http/timeline"; @@ -10,6 +9,7 @@ import UserTimelineLogo from "../common/UserTimelineLogo"; import LoadFailReload from "../common/LoadFailReload"; import FlatButton from "../common/button/FlatButton"; import Card from "../common/Card"; +import Spinner from "../common/Spinner"; interface TimelineBoardItemProps { timeline: HttpTimelineInfo; @@ -249,7 +249,7 @@ const TimelineBoardUI: React.FC = (props) => { if (timelines === "loading") { return (
- +
); } else if (timelines === "offline") { diff --git a/FrontEnd/src/views/center/index.tsx b/FrontEnd/src/views/center/index.tsx index dfad082a..77bb6ec8 100644 --- a/FrontEnd/src/views/center/index.tsx +++ b/FrontEnd/src/views/center/index.tsx @@ -21,7 +21,7 @@ const HomePage: React.FC = () => { return ( <> -
+
{ const { t } = useTranslation(); diff --git a/FrontEnd/src/views/common/FullPage.tsx b/FrontEnd/src/views/common/FullPage.tsx deleted file mode 100644 index 1b59045a..00000000 --- a/FrontEnd/src/views/common/FullPage.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from "react"; -import classnames from "classnames"; - -export interface FullPageProps { - show: boolean; - onBack: () => void; - contentContainerClassName?: string; -} - -const FullPage: React.FC = ({ - show, - onBack, - children, - contentContainerClassName, -}) => { - return ( -
-
- -
-
- {children} -
-
- ); -}; - -export default FullPage; diff --git a/FrontEnd/src/views/common/LoadingPage.tsx b/FrontEnd/src/views/common/LoadingPage.tsx index 590fafa0..8c1e681a 100644 --- a/FrontEnd/src/views/common/LoadingPage.tsx +++ b/FrontEnd/src/views/common/LoadingPage.tsx @@ -1,10 +1,11 @@ import React from "react"; -import { Spinner } from "react-bootstrap"; + +import Spinner from "./Spinner"; const LoadingPage: React.FC = () => { return (
- +
); }; diff --git a/FrontEnd/src/views/common/SearchInput.css b/FrontEnd/src/views/common/SearchInput.css new file mode 100644 index 00000000..2943b3a2 --- /dev/null +++ b/FrontEnd/src/views/common/SearchInput.css @@ -0,0 +1,4 @@ +.cru-search-input { + display: flex; + flex-wrap: wrap; +} diff --git a/FrontEnd/src/views/common/SearchInput.tsx b/FrontEnd/src/views/common/SearchInput.tsx index fff11b95..da3f1c19 100644 --- a/FrontEnd/src/views/common/SearchInput.tsx +++ b/FrontEnd/src/views/common/SearchInput.tsx @@ -4,6 +4,8 @@ import { useTranslation } from "react-i18next"; import LoadingButton from "./button/LoadingButton"; +import "./SearchInput.css"; + export interface SearchInputProps { value: string; onChange: (value: string) => void; diff --git a/FrontEnd/src/views/common/TabPages.tsx b/FrontEnd/src/views/common/TabPages.tsx deleted file mode 100644 index b7a9fb36..00000000 --- a/FrontEnd/src/views/common/TabPages.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; - -import { convertI18nText, I18nText, UiLogicError } from "@/common"; - -export interface TabPage { - id: string; - tabText: I18nText; - page: React.ReactNode; -} - -export interface TabPagesProps { - pages: TabPage[]; - actions?: React.ReactNode; - className?: string; - style?: React.CSSProperties; - navClassName?: string; - navStyle?: React.CSSProperties; - pageContainerClassName?: string; - pageContainerStyle?: React.CSSProperties; -} - -const TabPages: React.FC = ({ - pages, - actions, - className, - style, - navClassName, - navStyle, - pageContainerClassName, - pageContainerStyle, -}) => { - // TODO: - - if (pages.length === 0) { - throw new UiLogicError("Page list can't be empty."); - } - - const { t } = useTranslation(); - - const [tab, setTab] = React.useState(pages[0].id); - - const currentPage = pages.find((p) => p.id === tab); - - if (currentPage == null) { - throw new UiLogicError("Current tab value is bad."); - } - - return ( -
-
- {currentPage.page} -
-
- ); -}; - -export default TabPages; diff --git a/FrontEnd/src/views/common/alert/alert.css b/FrontEnd/src/views/common/alert/alert.css new file mode 100644 index 00000000..12ce294e --- /dev/null +++ b/FrontEnd/src/views/common/alert/alert.css @@ -0,0 +1,4 @@ +.alert-container { + position: fixed; + z-index: 1040; +} diff --git a/FrontEnd/src/views/common/alert/alert.sass b/FrontEnd/src/views/common/alert/alert.sass deleted file mode 100644 index c3560b87..00000000 --- a/FrontEnd/src/views/common/alert/alert.sass +++ /dev/null @@ -1,15 +0,0 @@ -.alert-container - position: fixed - z-index: $zindex-popover - -@include media-breakpoint-up(sm) - .alert-container - bottom: 0 - right: 0 - -@include media-breakpoint-down(sm) - .alert-container - bottom: 0 - right: 0 - left: 0 - text-align: center diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css index e69de29b..b6df222f 100644 --- a/FrontEnd/src/views/common/button/Button.css +++ b/FrontEnd/src/views/common/button/Button.css @@ -0,0 +1,72 @@ +.cru-button { + color: white; + cursor: pointer; + padding: 0.2em 0.5em; + border-radius: 0.2em; + border: none; + transition: all 0.6s; +} + +.cru-button.primary { + background-color: var(--tl-primary-color); +} + +.cru-button.primary:hover { + background-color: var(--tl-primary-f1-color); +} + +.cru-button.primary:active { + background-color: var(--tl-primary-f2-color); +} + +.cru-button.primary.disabled { + background-color: var(--tl-primary-f3-color); +} + +.cru-button.secondary { + background-color: var(--tl-secondary-color); +} + +.cru-button.secondary:hover { + background-color: var(--tl-secondary-f1-color); +} + +.cru-button.secondary:active { + background-color: var(--tl-secondary-f2-color); +} + +.cru-button.secondary.disabled { + background-color: var(--tl-secondary-f3-color); +} + +.cru-button.success { + background-color: var(--tl-success-color); +} + +.cru-button.success:hover { + background-color: var(--tl-success-f1-color); +} + +.cru-button.success:active { + background-color: var(--tl-success-f2-color); +} + +.cru-button.success.disabled { + background-color: var(--tl-success-f3-color); +} + +.cru-button.danger { + background-color: var(--tl-danger-color); +} + +.cru-button.danger:hover { + background-color: var(--tl-danger-f1-color); +} + +.cru-button.danger:active { + background-color: var(--tl-danger-f2-color); +} + +.cru-button.danger.disabled { + background-color: var(--tl-danger-f3-color); +} diff --git a/FrontEnd/src/views/common/button/Button.tsx b/FrontEnd/src/views/common/button/Button.tsx index 11710647..a39ef8a7 100644 --- a/FrontEnd/src/views/common/button/Button.tsx +++ b/FrontEnd/src/views/common/button/Button.tsx @@ -6,16 +6,19 @@ import { calculateProps, CommonButtonProps } from "./common"; import "./Button.css"; function _Button( - props: CommonButtonProps & { customButtonClassName?: string }, + props: CommonButtonProps & { + outline?: boolean; + customButtonClassName?: string; + }, ref: React.ForwardedRef ): React.ReactElement | null { const { t } = useTranslation(); - const { customButtonClassName, ...otherProps } = props; + const { customButtonClassName, outline, ...otherProps } = props; const { newProps, children } = calculateProps( otherProps, - customButtonClassName ?? "cru-button", + customButtonClassName ?? "cru-button" + (outline ? " outline" : ""), t ); diff --git a/FrontEnd/src/views/common/button/FlatButton.css b/FrontEnd/src/views/common/button/FlatButton.css index 522563b9..c3c0dbb3 100644 --- a/FrontEnd/src/views/common/button/FlatButton.css +++ b/FrontEnd/src/views/common/button/FlatButton.css @@ -20,7 +20,7 @@ } .cru-flat-button.primary.disabled { - color: var(--tl-primary-lighter-color); + color: var(--tl-primary-l1-color); } .cru-flat-button.secondary { @@ -28,7 +28,7 @@ } .cru-flat-button.secondary.disabled { - color: var(--tl-secondary-lighter-color); + color: var(--tl-secondary-l1-color); } .cru-flat-button.success { @@ -36,7 +36,7 @@ } .cru-flat-button.success.disabled { - color: var(--tl-success-lighter-color); + color: var(--tl-success-l1-color); } .cru-flat-button.danger { @@ -44,5 +44,5 @@ } .cru-flat-button.danger.disabled { - color: var(--tl-danger-ligher-color); + color: var(--tl-danger-l1-color); } diff --git a/FrontEnd/src/views/common/button/TextButton.css b/FrontEnd/src/views/common/button/TextButton.css index dc5abaaa..d267fb38 100644 --- a/FrontEnd/src/views/common/button/TextButton.css +++ b/FrontEnd/src/views/common/button/TextButton.css @@ -8,7 +8,7 @@ } .cru-text-button.primary:hover { - color: var(--tl-primary-lighter-color); + color: var(--tl-primary-l1-color); } .cru-text-button.secondary { @@ -16,7 +16,7 @@ } .cru-text-button.secondary:hover { - color: var(--tl-secondary-lighter-color); + color: var(--tl-secondary-l1-color); } .cru-text-button.success { @@ -24,7 +24,7 @@ } .cru-text-button.success:hover { - color: var(--tl-success-lighter-color); + color: var(--tl-success-l1-color); } .cru-text-button.danger { @@ -32,5 +32,5 @@ } .cru-text-button.danger:hover { - color: var(--tl-danger-lighter-color); + color: var(--tl-danger-l1-color); } diff --git a/FrontEnd/src/views/common/dailog/FullPageDialog.tsx b/FrontEnd/src/views/common/dailog/FullPageDialog.tsx new file mode 100644 index 00000000..88c90bbc --- /dev/null +++ b/FrontEnd/src/views/common/dailog/FullPageDialog.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import classnames from "classnames"; + +export interface FullPageDialogProps { + show: boolean; + onBack: () => void; + contentContainerClassName?: string; +} + +const FullPageDialog: React.FC = ({ + show, + onBack, + children, + contentContainerClassName, +}) => { + return ( +
+
+ +
+
+ {children} +
+
+ ); +}; + +export default FullPageDialog; diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index bfd82b58..f580c781 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -37,96 +37,6 @@ touch-action: none; } -.app-bar { - display: flex; - align-items: center; - height: 56px; - position: fixed; - z-index: 1030; - top: 0; - left: 0; - right: 0; - background-color: var(--tl-primary-color); - transition: background-color 1s; -} -.app-bar a { - color: var(--tl-text-on-primary-inactive-color); - text-decoration: none; - margin: 0 1em; -} -.app-bar a:hover { - color: var(--tl-text-on-primary-color); -} -.app-bar a.active { - color: var(--tl-text-on-primary-color); -} - -.app-bar-brand { - display: flex; - align-items: center; -} - -.app-bar-brand-icon { - height: 2em; -} - -.app-bar-main-area { - display: flex; - flex-grow: 1; -} - -.app-bar-link-area { - display: flex; - align-items: center; - flex-shrink: 0; -} - -.app-bar-user-area { - display: flex; - align-items: center; - flex-shrink: 0; - margin-left: auto; -} - -.small-screen .app-bar-main-area { - position: absolute; - top: 56px; - left: 0; - right: 0; - transform-origin: top; - transition: transform 0.6s, background-color 1s; - background-color: var(--tl-primary-color); - flex-direction: column; -} -.small-screen .app-bar-main-area.app-bar-collapse { - transform: scale(1, 0); -} -.small-screen .app-bar-main-area a { - text-align: left; - padding: 0.5em 0.5em; -} -.small-screen .app-bar-link-area { - flex-direction: column; - align-items: stretch; -} -.small-screen .app-bar-user-area { - flex-direction: column; - align-items: stretch; - margin-left: unset; -} -.small-screen .app-bar-avatar { - align-self: flex-end; -} - -.app-bar-toggler { - margin-left: auto; - font-size: 2em; - margin-right: 1em; - color: var(--tl-text-on-primary-color); - cursor: pointer; - user-select: none; -} - .cru-skeleton { padding: 0 1em; } @@ -247,10 +157,7 @@ align-items: center; } -.cru-search-input { - display: flex; - flex-wrap: wrap; -} + .alert-container { position: fixed; diff --git a/FrontEnd/src/views/common/tab/TabPages.tsx b/FrontEnd/src/views/common/tab/TabPages.tsx new file mode 100644 index 00000000..b7a9fb36 --- /dev/null +++ b/FrontEnd/src/views/common/tab/TabPages.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; + +import { convertI18nText, I18nText, UiLogicError } from "@/common"; + +export interface TabPage { + id: string; + tabText: I18nText; + page: React.ReactNode; +} + +export interface TabPagesProps { + pages: TabPage[]; + actions?: React.ReactNode; + className?: string; + style?: React.CSSProperties; + navClassName?: string; + navStyle?: React.CSSProperties; + pageContainerClassName?: string; + pageContainerStyle?: React.CSSProperties; +} + +const TabPages: React.FC = ({ + pages, + actions, + className, + style, + navClassName, + navStyle, + pageContainerClassName, + pageContainerStyle, +}) => { + // TODO: + + if (pages.length === 0) { + throw new UiLogicError("Page list can't be empty."); + } + + const { t } = useTranslation(); + + const [tab, setTab] = React.useState(pages[0].id); + + const currentPage = pages.find((p) => p.id === tab); + + if (currentPage == null) { + throw new UiLogicError("Current tab value is bad."); + } + + return ( +
+
+ {currentPage.page} +
+
+ ); +}; + +export default TabPages; diff --git a/FrontEnd/src/views/common/tab/Tabs.tsx b/FrontEnd/src/views/common/tab/Tabs.tsx new file mode 100644 index 00000000..29ebcbd8 --- /dev/null +++ b/FrontEnd/src/views/common/tab/Tabs.tsx @@ -0,0 +1,19 @@ +import React from "react"; + +import { I18nText } from "@/common"; + +export interface Tab { + name: string; + text: I18nText; + link?: string; + onClick?: () => void; +} + +export interface TabsProps { + activeTabName?: string; + tabs: Tab[]; +} + +export default function Tabs(props: TabsProps): React.ReactElement | null { + return
; +} diff --git a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx index 9f0753b9..b16bf43d 100644 --- a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx +++ b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx @@ -9,7 +9,7 @@ import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; import TimelinePostBuilder from "@/services/TimelinePostBuilder"; import FlatButton from "../common/button/FlatButton"; -import TabPages from "../common/TabPages"; +import TabPages from "../common/tab/TabPages"; import ConfirmDialog from "../common/dailog/ConfirmDialog"; import Spinner from "../common/Spinner"; diff --git a/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx b/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx index 851dfa55..78057be2 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx @@ -18,7 +18,7 @@ import { TimelineMemberDialog } from "./TimelineMember"; import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; import ConnectionStatusBadge from "./ConnectionStatusBadge"; import { MenuItems, PopupMenu } from "../common/Menu"; -import FullPage from "../common/FullPage"; +import FullPageDialog from "../common/dailog/FullPageDialog"; import Card from "../common/Card"; export interface TimelineCardTemplateProps extends TimelinePageCardProps { @@ -120,13 +120,13 @@ const TimelinePageCardTemplate: React.FC = ({
{isSmallScreen ? ( - {content} - + ) : (
{content}
)} diff --git a/FrontEnd/src/views/timeline-common/index.css b/FrontEnd/src/views/timeline-common/index.css index e38d0ba7..b78564a3 100644 --- a/FrontEnd/src/views/timeline-common/index.css +++ b/FrontEnd/src/views/timeline-common/index.css @@ -13,19 +13,19 @@ @keyframes timeline-line-node-noncurrent { to { - box-shadow: 0 0 20px 3px var(--tl-primary-lighter-color); + box-shadow: 0 0 20px 3px var(--tl-primary-l1-color); } } @keyframes timeline-line-node-current { to { - box-shadow: 0 0 20px 3px var(--tl-primary-enhance-lighter-color); + box-shadow: 0 0 20px 3px var(--tl-primary-enhance-l1-color); } } @keyframes timeline-line-node-loading { to { - box-shadow: 0 0 20px 3px var(--tl-primary-lighter-color); + box-shadow: 0 0 20px 3px var(--tl-primary-l1-color); } } -- cgit v1.2.3 From de7df0e4fb38863b830a104a5a38d0e5247679f8 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 29 Jun 2021 23:50:55 +0800 Subject: ... --- FrontEnd/index.html | 2 +- FrontEnd/src/index.css | 127 +----------- FrontEnd/src/palette.ts | 14 +- FrontEnd/src/views/common/AppBar.css | 12 +- FrontEnd/src/views/common/Card.css | 2 +- FrontEnd/src/views/common/Menu.tsx | 80 -------- FrontEnd/src/views/common/button/Button.css | 32 +-- FrontEnd/src/views/common/button/FlatButton.css | 16 +- FrontEnd/src/views/common/button/TextButton.css | 16 +- FrontEnd/src/views/common/dailog/Dialog.css | 22 ++ FrontEnd/src/views/common/dailog/Dialog.tsx | 30 ++- FrontEnd/src/views/common/index.css | 225 ++++++++++++++------- FrontEnd/src/views/common/menu/Menu.css | 35 ++++ FrontEnd/src/views/common/menu/Menu.tsx | 66 ++++++ FrontEnd/src/views/common/menu/PopupMenu.tsx | 17 ++ .../timeline-common/TimelinePageCardTemplate.tsx | 2 +- .../src/views/timeline-common/TimelinePostEdit.tsx | 2 +- FrontEnd/src/views/timeline-common/index.css | 24 +-- 18 files changed, 384 insertions(+), 340 deletions(-) delete mode 100644 FrontEnd/src/views/common/Menu.tsx create mode 100644 FrontEnd/src/views/common/menu/Menu.css create mode 100644 FrontEnd/src/views/common/menu/Menu.tsx create mode 100644 FrontEnd/src/views/common/menu/PopupMenu.tsx (limited to 'FrontEnd/src/views/common/button') diff --git a/FrontEnd/index.html b/FrontEnd/index.html index ccd6a21b..3cc5a580 100644 --- a/FrontEnd/index.html +++ b/FrontEnd/index.html @@ -24,7 +24,7 @@
-
+
diff --git a/FrontEnd/src/index.css b/FrontEnd/src/index.css index 4adc1844..14f0bc43 100644 --- a/FrontEnd/src/index.css +++ b/FrontEnd/src/index.css @@ -4,120 +4,8 @@ @import "./views/common/index.css"; -:root { - --tl-background-color: #f8f9fa; - - --tl-primary-color: rgb(0, 123, 255); - --tl-primary-l1-color: rgb(26, 136, 255); - --tl-primary-l2-color: rgb(51, 149, 255); - --tl-primary-l3-color: rgb(77, 163, 255); - --tl-primary-d1-color: rgb(0, 111, 230); - --tl-primary-d2-color: rgb(0, 98, 204); - --tl-primary-d3-color: rgb(0, 86, 179); - --tl-primary-f1-color: rgb(0, 111, 230); - --tl-primary-f2-color: rgb(0, 98, 204); - --tl-primary-f3-color: rgb(0, 86, 179); - --tl-primary-r1-color: rgb(26, 136, 255); - --tl-primary-r2-color: rgb(51, 149, 255); - --tl-primary-r3-color: rgb(77, 163, 255); - --tl-primary-enhance-color: rgb(77, 163, 255); - --tl-primary-enhance-l1-color: rgb(94, 172, 255); - --tl-primary-enhance-l2-color: rgb(112, 181, 255); - --tl-primary-enhance-l3-color: rgb(130, 190, 255); - --tl-primary-enhance-d1-color: rgb(43, 145, 255); - --tl-primary-enhance-d2-color: rgb(10, 128, 255); - --tl-primary-enhance-d3-color: rgb(0, 112, 232); - --tl-primary-enhance-f1-color: rgb(94, 172, 255); - --tl-primary-enhance-f2-color: rgb(112, 181, 255); - --tl-primary-enhance-f3-color: rgb(130, 190, 255); - --tl-primary-enhance-r1-color: rgb(43, 145, 255); - --tl-primary-enhance-r2-color: rgb(10, 128, 255); - --tl-primary-enhance-r3-color: rgb(0, 112, 232); - --tl-secondary-color: rgb(255, 0, 250); - --tl-secondary-l1-color: rgb(255, 26, 251); - --tl-secondary-l2-color: rgb(255, 51, 251); - --tl-secondary-l3-color: rgb(255, 77, 252); - --tl-secondary-d1-color: rgb(230, 0, 225); - --tl-secondary-d2-color: rgb(204, 0, 200); - --tl-secondary-d3-color: rgb(179, 0, 175); - --tl-secondary-f1-color: rgb(230, 0, 225); - --tl-secondary-f2-color: rgb(204, 0, 200); - --tl-secondary-f3-color: rgb(179, 0, 175); - --tl-secondary-r1-color: rgb(255, 26, 251); - --tl-secondary-r2-color: rgb(255, 51, 251); - --tl-secondary-r3-color: rgb(255, 77, 252); - --tl-text-primary-color: rgb(17, 17, 17); - --tl-text-primary-l1-color: rgb(41, 41, 41); - --tl-text-primary-l2-color: rgb(65, 65, 65); - --tl-text-primary-l3-color: rgb(88, 88, 88); - --tl-text-primary-d1-color: rgb(15, 15, 15); - --tl-text-primary-d2-color: rgb(14, 14, 14); - --tl-text-primary-d3-color: rgb(12, 12, 12); - --tl-text-primary-f1-color: rgb(15, 15, 15); - --tl-text-primary-f2-color: rgb(14, 14, 14); - --tl-text-primary-f3-color: rgb(12, 12, 12); - --tl-text-primary-r1-color: rgb(41, 41, 41); - --tl-text-primary-r2-color: rgb(65, 65, 65); - --tl-text-primary-r3-color: rgb(88, 88, 88); - --tl-text-on-primary-color: rgb(255, 255, 255); - --tl-text-on-primary-l1-color: rgb(255, 255, 255); - --tl-text-on-primary-l2-color: rgb(255, 255, 255); - --tl-text-on-primary-l3-color: rgb(255, 255, 255); - --tl-text-on-primary-d1-color: rgb(230, 230, 230); - --tl-text-on-primary-d2-color: rgb(204, 204, 204); - --tl-text-on-primary-d3-color: rgb(179, 179, 179); - --tl-text-on-primary-f1-color: rgb(255, 255, 255); - --tl-text-on-primary-f2-color: rgb(255, 255, 255); - --tl-text-on-primary-f3-color: rgb(255, 255, 255); - --tl-text-on-primary-r1-color: rgb(230, 230, 230); - --tl-text-on-primary-r2-color: rgb(204, 204, 204); - --tl-text-on-primary-r3-color: rgb(179, 179, 179); - --tl-danger-color: rgb(255, 0, 0); - --tl-danger-l1-color: rgb(255, 26, 26); - --tl-danger-l2-color: rgb(255, 51, 51); - --tl-danger-l3-color: rgb(255, 77, 77); - --tl-danger-d1-color: rgb(230, 0, 0); - --tl-danger-d2-color: rgb(204, 0, 0); - --tl-danger-d3-color: rgb(179, 0, 0); - --tl-danger-f1-color: rgb(230, 0, 0); - --tl-danger-f2-color: rgb(204, 0, 0); - --tl-danger-f3-color: rgb(179, 0, 0); - --tl-danger-r1-color: rgb(255, 26, 26); - --tl-danger-r2-color: rgb(255, 51, 51); - --tl-danger-r3-color: rgb(255, 77, 77); - --tl-success-color: rgb(0, 128, 0); - --tl-success-l1-color: rgb(0, 166, 0); - --tl-success-l2-color: rgb(0, 204, 0); - --tl-success-l3-color: rgb(0, 243, 0); - --tl-success-d1-color: rgb(0, 115, 0); - --tl-success-d2-color: rgb(0, 102, 0); - --tl-success-d3-color: rgb(0, 90, 0); - --tl-success-f1-color: rgb(0, 115, 0); - --tl-success-f2-color: rgb(0, 102, 0); - --tl-success-f3-color: rgb(0, 90, 0); - --tl-success-r1-color: rgb(0, 166, 0); - --tl-success-r2-color: rgb(0, 204, 0); - --tl-success-r3-color: rgb(0, 243, 0); -} - -.tl-color-primary { - color: var(--tl-primary-color); -} - -.tl-color-secondary { - color: var(--tl-secondary-color); -} - -.tl-color-success { - color: var(--tl-success-color); -} - -.tl-color-danger { - color: var(--tl-danger-color); -} - body { - background: var(--tl-background-color); + background: var(--cru-background-color); } small { @@ -129,19 +17,6 @@ small { flex-shrink: 0; } -.icon-button { - font-size: 1.4rem; - cursor: pointer; -} - -.icon-button.large { - font-size: 1.6rem; -} - -.icon-button.primary-enhance { - color: var(--tl-primary-enhance-color); -} - .cursor-pointer { cursor: pointer; } diff --git a/FrontEnd/src/palette.ts b/FrontEnd/src/palette.ts index fa99364f..fb54f042 100644 --- a/FrontEnd/src/palette.ts +++ b/FrontEnd/src/palette.ts @@ -36,6 +36,8 @@ const paletteColorList = [ "secondary", "text-primary", "text-on-primary", + "text-on-primary-enhance", + "text-on-secondary", "danger", "success", ] as const; @@ -98,6 +100,12 @@ export function generatePalette(options: { "text-on-primary": generatePaletteColor( p.lightness() > 60 ? "black" : "white" ), + "text-on-primary-enhance": generatePaletteColor( + pe.lightness() > 60 ? "black" : "white" + ), + "text-on-secondary": generatePaletteColor( + s.lightness() > 60 ? "black" : "white" + ), danger: generatePaletteColor("red"), success: generatePaletteColor("green"), }; @@ -108,7 +116,7 @@ export function generatePaletteCSS(palette: Palette): string { for (const colorType of paletteColorList) { const paletteColor = palette[colorType]; for (const variant in paletteColor) { - let key = `--tl-${colorType}`; + let key = `--cru-${colorType}`; if (variant !== "color") key += `-${variant}`; key += "-color"; colors.push([key, paletteColor[variant]]); @@ -122,8 +130,8 @@ export function generatePaletteCSS(palette: Palette): string { const paletteSubject: BehaviorSubject = new BehaviorSubject( - // generatePalette({ primary: "rgb(0, 123, 255)" }) - null + generatePalette({ primary: "rgb(0, 123, 255)" }) + // null ); export const palette$: Observable = diff --git a/FrontEnd/src/views/common/AppBar.css b/FrontEnd/src/views/common/AppBar.css index 16572817..e4bdf852 100644 --- a/FrontEnd/src/views/common/AppBar.css +++ b/FrontEnd/src/views/common/AppBar.css @@ -7,7 +7,7 @@ top: 0; left: 0; right: 0; - background-color: var(--tl-primary-color); + background-color: var(--cru-primary-color); transition: background-color 1s; } @@ -16,16 +16,16 @@ } .app-bar a { - color: var(--tl-text-on-primary-r1-color); + color: var(--cru-text-on-primary-r1-color); text-decoration: none; margin: 0 1em; transition: color 1s; } .app-bar a:hover { - color: var(--tl-text-on-primary-color); + color: var(--cru-text-on-primary-color); } .app-bar a.active { - color: var(--tl-text-on-primary-color); + color: var(--cru-text-on-primary-color); } .app-bar-brand { @@ -62,7 +62,7 @@ right: 0; transform-origin: top; transition: transform 0.6s, background-color 1s; - background-color: var(--tl-primary-color); + background-color: var(--cru-primary-color); flex-direction: column; } .small-screen .app-bar-main-area.app-bar-collapse { @@ -89,7 +89,7 @@ margin-left: auto; font-size: 2em; margin-right: 1em; - color: var(--tl-text-on-primary-color); + color: var(--cru-text-on-primary-color); cursor: pointer; user-select: none; } diff --git a/FrontEnd/src/views/common/Card.css b/FrontEnd/src/views/common/Card.css index fb90bd59..6de0dd8e 100644 --- a/FrontEnd/src/views/common/Card.css +++ b/FrontEnd/src/views/common/Card.css @@ -11,5 +11,5 @@ } .cru-card:hover { - border-color: var(--tl-primary-color); + border-color: var(--cru-primary-color); } diff --git a/FrontEnd/src/views/common/Menu.tsx b/FrontEnd/src/views/common/Menu.tsx deleted file mode 100644 index a5d2ec2c..00000000 --- a/FrontEnd/src/views/common/Menu.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from "react"; -import classnames from "classnames"; -import { useTranslation } from "react-i18next"; - -import { convertI18nText, I18nText } from "@/common"; -import { PaletteColorType } from "@/palette"; - -export type MenuItem = - | { - type: "divider"; - } - | { - type: "button"; - text: I18nText; - iconClassName?: string; - color?: PaletteColorType; - onClick: () => void; - }; - -export type MenuItems = MenuItem[]; - -export interface MenuProps { - items: MenuItems; - className?: string; - onItemClicked?: () => void; -} - -const Menu: React.FC = ({ items, className, onItemClicked }) => { - const { t } = useTranslation(); - - return ( -
- {items.map((item, index) => { - if (item.type === "divider") { - return
; - } else { - return ( -
{ - item.onClick(); - onItemClicked?.(); - }} - > - {item.iconClassName != null ? ( - - ) : null} - {convertI18nText(item.text, t)} -
- ); - } - })} -
- ); -}; - -export default Menu; - -export interface PopupMenuProps { - items: MenuItems; - children: React.ReactElement; -} - -export const PopupMenu: React.FC = ({ items, children }) => { - const [show, setShow] = React.useState(false); - const toggle = (): void => setShow(!show); - - // TODO: - - return setShow(false)} />; -}; diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css index b6df222f..8b5b74a7 100644 --- a/FrontEnd/src/views/common/button/Button.css +++ b/FrontEnd/src/views/common/button/Button.css @@ -8,65 +8,65 @@ } .cru-button.primary { - background-color: var(--tl-primary-color); + background-color: var(--cru-primary-color); } .cru-button.primary:hover { - background-color: var(--tl-primary-f1-color); + background-color: var(--cru-primary-f1-color); } .cru-button.primary:active { - background-color: var(--tl-primary-f2-color); + background-color: var(--cru-primary-f2-color); } .cru-button.primary.disabled { - background-color: var(--tl-primary-f3-color); + background-color: var(--cru-primary-f3-color); } .cru-button.secondary { - background-color: var(--tl-secondary-color); + background-color: var(--cru-secondary-color); } .cru-button.secondary:hover { - background-color: var(--tl-secondary-f1-color); + background-color: var(--cru-secondary-f1-color); } .cru-button.secondary:active { - background-color: var(--tl-secondary-f2-color); + background-color: var(--cru-secondary-f2-color); } .cru-button.secondary.disabled { - background-color: var(--tl-secondary-f3-color); + background-color: var(--cru-secondary-f3-color); } .cru-button.success { - background-color: var(--tl-success-color); + background-color: var(--cru-success-color); } .cru-button.success:hover { - background-color: var(--tl-success-f1-color); + background-color: var(--cru-success-f1-color); } .cru-button.success:active { - background-color: var(--tl-success-f2-color); + background-color: var(--cru-success-f2-color); } .cru-button.success.disabled { - background-color: var(--tl-success-f3-color); + background-color: var(--cru-success-f3-color); } .cru-button.danger { - background-color: var(--tl-danger-color); + background-color: var(--cru-danger-color); } .cru-button.danger:hover { - background-color: var(--tl-danger-f1-color); + background-color: var(--cru-danger-f1-color); } .cru-button.danger:active { - background-color: var(--tl-danger-f2-color); + background-color: var(--cru-danger-f2-color); } .cru-button.danger.disabled { - background-color: var(--tl-danger-f3-color); + background-color: var(--cru-danger-f3-color); } diff --git a/FrontEnd/src/views/common/button/FlatButton.css b/FrontEnd/src/views/common/button/FlatButton.css index c3c0dbb3..986fb061 100644 --- a/FrontEnd/src/views/common/button/FlatButton.css +++ b/FrontEnd/src/views/common/button/FlatButton.css @@ -16,33 +16,33 @@ } .cru-flat-button.primary { - color: var(--tl-primary-color); + color: var(--cru-primary-color); } .cru-flat-button.primary.disabled { - color: var(--tl-primary-l1-color); + color: var(--cru-primary-l1-color); } .cru-flat-button.secondary { - color: var(--tl-secondary-color); + color: var(--cru-secondary-color); } .cru-flat-button.secondary.disabled { - color: var(--tl-secondary-l1-color); + color: var(--cru-secondary-l1-color); } .cru-flat-button.success { - color: var(--tl-success-color); + color: var(--cru-success-color); } .cru-flat-button.success.disabled { - color: var(--tl-success-l1-color); + color: var(--cru-success-l1-color); } .cru-flat-button.danger { - color: var(--tl-danger-color); + color: var(--cru-danger-color); } .cru-flat-button.danger.disabled { - color: var(--tl-danger-l1-color); + color: var(--cru-danger-l1-color); } diff --git a/FrontEnd/src/views/common/button/TextButton.css b/FrontEnd/src/views/common/button/TextButton.css index d267fb38..f5c8aefa 100644 --- a/FrontEnd/src/views/common/button/TextButton.css +++ b/FrontEnd/src/views/common/button/TextButton.css @@ -4,33 +4,33 @@ } .cru-text-button.primary { - color: var(--tl-primary-color); + color: var(--cru-primary-color); } .cru-text-button.primary:hover { - color: var(--tl-primary-l1-color); + color: var(--cru-primary-l1-color); } .cru-text-button.secondary { - color: var(--tl-secondary-color); + color: var(--cru-secondary-color); } .cru-text-button.secondary:hover { - color: var(--tl-secondary-l1-color); + color: var(--cru-secondary-l1-color); } .cru-text-button.success { - color: var(--tl-success-color); + color: var(--cru-success-color); } .cru-text-button.success:hover { - color: var(--tl-success-l1-color); + color: var(--cru-success-l1-color); } .cru-text-button.danger { - color: var(--tl-danger-color); + color: var(--cru-danger-color); } .cru-text-button.danger:hover { - color: var(--tl-danger-l1-color); + color: var(--cru-danger-l1-color); } diff --git a/FrontEnd/src/views/common/dailog/Dialog.css b/FrontEnd/src/views/common/dailog/Dialog.css index e69de29b..abce04fd 100644 --- a/FrontEnd/src/views/common/dailog/Dialog.css +++ b/FrontEnd/src/views/common/dailog/Dialog.css @@ -0,0 +1,22 @@ +.cru-dialog-overlay { + position: fixed; + z-index: 1040; + left: 0; + top: 0; + right: 0; + bottom: 0; + background-color: rgba(255, 255, 255, 0.92); + + display: flex; + justify-content: center; + align-items: center; +} + +.cru-dialog-container { + max-width: 100%; + + border: var(--cru-primary-color) 1px solid; + border-radius: 5px; + padding: 1.5em; + background-color: white; +} diff --git a/FrontEnd/src/views/common/dailog/Dialog.tsx b/FrontEnd/src/views/common/dailog/Dialog.tsx index 5a3902c4..ee58080f 100644 --- a/FrontEnd/src/views/common/dailog/Dialog.tsx +++ b/FrontEnd/src/views/common/dailog/Dialog.tsx @@ -1,13 +1,39 @@ import React from "react"; +import ReactDOM from "react-dom"; + +import "./Dialog.css"; export interface DialogProps { onClose: () => void; open?: boolean; children?: React.ReactNode; + disableCloseOnClickOnOverlay?: boolean; } export default function Dialog(props: DialogProps): React.ReactElement | null { - const { open, onClose, children } = props; + const { open, onClose, children, disableCloseOnClickOnOverlay } = props; - return
{children}
; + return open + ? ReactDOM.createPortal( +
{ + onClose(); + } + } + > +
e.stopPropagation()} + > + {children} +
+
, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + document.getElementById("portal")! + ) + : null; } diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index aaaaad58..e2769fe4 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -1,3 +1,152 @@ +:root { + --cru-primary-color: rgb(255, 105, 0); + --cru-primary-l1-color: rgb(255, 120, 26); + --cru-primary-l2-color: rgb(255, 135, 51); + --cru-primary-l3-color: rgb(255, 150, 77); + --cru-primary-d1-color: rgb(230, 95, 0); + --cru-primary-d2-color: rgb(204, 84, 0); + --cru-primary-d3-color: rgb(179, 74, 0); + --cru-primary-f1-color: rgb(230, 95, 0); + --cru-primary-f2-color: rgb(204, 84, 0); + --cru-primary-f3-color: rgb(179, 74, 0); + --cru-primary-r1-color: rgb(255, 120, 26); + --cru-primary-r2-color: rgb(255, 135, 51); + --cru-primary-r3-color: rgb(255, 150, 77); + --cru-primary-enhance-color: rgb(255, 150, 77); + --cru-primary-enhance-l1-color: rgb(255, 161, 94); + --cru-primary-enhance-l2-color: rgb(255, 171, 112); + --cru-primary-enhance-l3-color: rgb(255, 182, 130); + --cru-primary-enhance-d1-color: rgb(255, 131, 43); + --cru-primary-enhance-d2-color: rgb(255, 111, 10); + --cru-primary-enhance-d3-color: rgb(232, 96, 0); + --cru-primary-enhance-f1-color: rgb(255, 161, 94); + --cru-primary-enhance-f2-color: rgb(255, 171, 112); + --cru-primary-enhance-f3-color: rgb(255, 182, 130); + --cru-primary-enhance-r1-color: rgb(255, 131, 43); + --cru-primary-enhance-r2-color: rgb(255, 111, 10); + --cru-primary-enhance-r3-color: rgb(232, 96, 0); + --cru-secondary-color: rgb(23, 255, 0); + --cru-secondary-l1-color: rgb(46, 255, 26); + --cru-secondary-l2-color: rgb(69, 255, 51); + --cru-secondary-l3-color: rgb(92, 255, 77); + --cru-secondary-d1-color: rgb(20, 230, 0); + --cru-secondary-d2-color: rgb(18, 204, 0); + --cru-secondary-d3-color: rgb(16, 179, 0); + --cru-secondary-f1-color: rgb(20, 230, 0); + --cru-secondary-f2-color: rgb(18, 204, 0); + --cru-secondary-f3-color: rgb(16, 179, 0); + --cru-secondary-r1-color: rgb(46, 255, 26); + --cru-secondary-r2-color: rgb(69, 255, 51); + --cru-secondary-r3-color: rgb(92, 255, 77); + --cru-text-primary-color: rgb(17, 17, 17); + --cru-text-primary-l1-color: rgb(41, 41, 41); + --cru-text-primary-l2-color: rgb(65, 65, 65); + --cru-text-primary-l3-color: rgb(88, 88, 88); + --cru-text-primary-d1-color: rgb(15, 15, 15); + --cru-text-primary-d2-color: rgb(14, 14, 14); + --cru-text-primary-d3-color: rgb(12, 12, 12); + --cru-text-primary-f1-color: rgb(15, 15, 15); + --cru-text-primary-f2-color: rgb(14, 14, 14); + --cru-text-primary-f3-color: rgb(12, 12, 12); + --cru-text-primary-r1-color: rgb(41, 41, 41); + --cru-text-primary-r2-color: rgb(65, 65, 65); + --cru-text-primary-r3-color: rgb(88, 88, 88); + --cru-text-on-primary-color: rgb(255, 255, 255); + --cru-text-on-primary-l1-color: rgb(255, 255, 255); + --cru-text-on-primary-l2-color: rgb(255, 255, 255); + --cru-text-on-primary-l3-color: rgb(255, 255, 255); + --cru-text-on-primary-d1-color: rgb(230, 230, 230); + --cru-text-on-primary-d2-color: rgb(204, 204, 204); + --cru-text-on-primary-d3-color: rgb(179, 179, 179); + --cru-text-on-primary-f1-color: rgb(255, 255, 255); + --cru-text-on-primary-f2-color: rgb(255, 255, 255); + --cru-text-on-primary-f3-color: rgb(255, 255, 255); + --cru-text-on-primary-r1-color: rgb(230, 230, 230); + --cru-text-on-primary-r2-color: rgb(204, 204, 204); + --cru-text-on-primary-r3-color: rgb(179, 179, 179); + --cru-text-on-primary-enhance-color: rgb(0, 0, 0); + --cru-text-on-primary-enhance-l1-color: rgb(26, 26, 26); + --cru-text-on-primary-enhance-l2-color: rgb(51, 51, 51); + --cru-text-on-primary-enhance-l3-color: rgb(77, 77, 77); + --cru-text-on-primary-enhance-d1-color: rgb(0, 0, 0); + --cru-text-on-primary-enhance-d2-color: rgb(0, 0, 0); + --cru-text-on-primary-enhance-d3-color: rgb(0, 0, 0); + --cru-text-on-primary-enhance-f1-color: rgb(0, 0, 0); + --cru-text-on-primary-enhance-f2-color: rgb(0, 0, 0); + --cru-text-on-primary-enhance-f3-color: rgb(0, 0, 0); + --cru-text-on-primary-enhance-r1-color: rgb(26, 26, 26); + --cru-text-on-primary-enhance-r2-color: rgb(51, 51, 51); + --cru-text-on-primary-enhance-r3-color: rgb(77, 77, 77); + --cru-text-on-secondary-color: rgb(255, 255, 255); + --cru-text-on-secondary-l1-color: rgb(255, 255, 255); + --cru-text-on-secondary-l2-color: rgb(255, 255, 255); + --cru-text-on-secondary-l3-color: rgb(255, 255, 255); + --cru-text-on-secondary-d1-color: rgb(230, 230, 230); + --cru-text-on-secondary-d2-color: rgb(204, 204, 204); + --cru-text-on-secondary-d3-color: rgb(179, 179, 179); + --cru-text-on-secondary-f1-color: rgb(255, 255, 255); + --cru-text-on-secondary-f2-color: rgb(255, 255, 255); + --cru-text-on-secondary-f3-color: rgb(255, 255, 255); + --cru-text-on-secondary-r1-color: rgb(230, 230, 230); + --cru-text-on-secondary-r2-color: rgb(204, 204, 204); + --cru-text-on-secondary-r3-color: rgb(179, 179, 179); + --cru-danger-color: rgb(255, 0, 0); + --cru-danger-l1-color: rgb(255, 26, 26); + --cru-danger-l2-color: rgb(255, 51, 51); + --cru-danger-l3-color: rgb(255, 77, 77); + --cru-danger-d1-color: rgb(230, 0, 0); + --cru-danger-d2-color: rgb(204, 0, 0); + --cru-danger-d3-color: rgb(179, 0, 0); + --cru-danger-f1-color: rgb(230, 0, 0); + --cru-danger-f2-color: rgb(204, 0, 0); + --cru-danger-f3-color: rgb(179, 0, 0); + --cru-danger-r1-color: rgb(255, 26, 26); + --cru-danger-r2-color: rgb(255, 51, 51); + --cru-danger-r3-color: rgb(255, 77, 77); + --cru-success-color: rgb(0, 128, 0); + --cru-success-l1-color: rgb(0, 166, 0); + --cru-success-l2-color: rgb(0, 204, 0); + --cru-success-l3-color: rgb(0, 243, 0); + --cru-success-d1-color: rgb(0, 115, 0); + --cru-success-d2-color: rgb(0, 102, 0); + --cru-success-d3-color: rgb(0, 90, 0); + --cru-success-f1-color: rgb(0, 115, 0); + --cru-success-f2-color: rgb(0, 102, 0); + --cru-success-f3-color: rgb(0, 90, 0); + --cru-success-r1-color: rgb(0, 166, 0); + --cru-success-r2-color: rgb(0, 204, 0); + --cru-success-r3-color: rgb(0, 243, 0); +} + +.tl-color-primary { + color: var(--cru-primary-color); +} + +.tl-color-secondary { + color: var(--cru-secondary-color); +} + +.tl-color-success { + color: var(--cru-success-color); +} + +.tl-color-danger { + color: var(--cru-danger-color); +} + +.icon-button { + font-size: 1.4rem; + cursor: pointer; +} + +.icon-button.large { + font-size: 1.6rem; +} + +.icon-button.primary-enhance { + color: var(--cru-primary-enhance-color); +} + .cru-avatar { width: 60px; height: 60px; @@ -49,7 +198,7 @@ left: 0; right: 0; z-index: 1; - background-color: var(--tl-primary-color); + background-color: var(--cru-primary-color); display: flex; align-items: center; } @@ -58,80 +207,6 @@ overflow: scroll; } -.cru-menu { - min-width: 200px; -} - -.cru-menu-item { - font-size: 1.2em; - padding: 0.5em 1.5em; - cursor: pointer; -} -.cru-menu-item.color-primary { - color: #0d6efd; -} -.cru-menu-item.color-primary:hover { - color: white; - background-color: #0d6efd; -} -.cru-menu-item.color-secondary { - color: #6c757d; -} -.cru-menu-item.color-secondary:hover { - color: white; - background-color: #6c757d; -} -.cru-menu-item.color-success { - color: #198754; -} -.cru-menu-item.color-success:hover { - color: white; - background-color: #198754; -} -.cru-menu-item.color-info { - color: #0dcaf0; -} -.cru-menu-item.color-info:hover { - color: white; - background-color: #0dcaf0; -} -.cru-menu-item.color-warning { - color: #ffc107; -} -.cru-menu-item.color-warning:hover { - color: white; - background-color: #ffc107; -} -.cru-menu-item.color-danger { - color: #dc3545; -} -.cru-menu-item.color-danger:hover { - color: white; - background-color: #dc3545; -} -.cru-menu-item.color-light { - color: #f8f9fa; -} -.cru-menu-item.color-light:hover { - color: white; - background-color: #f8f9fa; -} -.cru-menu-item.color-dark { - color: #212529; -} -.cru-menu-item.color-dark:hover { - color: white; - background-color: #212529; -} - -.cru-menu-item-icon { - margin-right: 1em; -} - -.cru-menu-divider { - border-top: 1px solid #e9ecef; -} - .cru-tab-pages-action-area { display: flex; align-items: center; diff --git a/FrontEnd/src/views/common/menu/Menu.css b/FrontEnd/src/views/common/menu/Menu.css new file mode 100644 index 00000000..0b455baa --- /dev/null +++ b/FrontEnd/src/views/common/menu/Menu.css @@ -0,0 +1,35 @@ +.cru-menu { + min-width: 200px; +} + +.cru-menu-item { + font-size: 1.2em; + padding: 0.5em 1.5em; + cursor: pointer; +} + +.cru-menu-item.color-primary { + color: var(--cru-primary-color); +} + +.cru-menu-item.color-primary:hover { + color: var(--cru-text-on-primary-color); + background-color: var(--cru-primary-color); +} + +.cru-menu-item.color-secondary { + color: var(--cru-secondary-color); +} + +.cru-menu-item.color-secondary:hover { + color: var(--cru-text-on-secondary-color); + background-color: var(--cru-secondary-color); +} + +.cru-menu-item-icon { + margin-right: 1em; +} + +.cru-menu-divider { + border-top: 1px solid #e9ecef; +} diff --git a/FrontEnd/src/views/common/menu/Menu.tsx b/FrontEnd/src/views/common/menu/Menu.tsx new file mode 100644 index 00000000..7523247d --- /dev/null +++ b/FrontEnd/src/views/common/menu/Menu.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import classnames from "classnames"; +import { useTranslation } from "react-i18next"; + +import { convertI18nText, I18nText } from "@/common"; +import { PaletteColorType } from "@/palette"; + +export type MenuItem = + | { + type: "divider"; + } + | { + type: "button"; + text: I18nText; + iconClassName?: string; + color?: PaletteColorType; + onClick: () => void; + }; + +export type MenuItems = MenuItem[]; + +export interface MenuProps { + items: MenuItems; + className?: string; + onItemClicked?: () => void; +} + +const Menu: React.FC = ({ items, className, onItemClicked }) => { + const { t } = useTranslation(); + + return ( +
+ {items.map((item, index) => { + if (item.type === "divider") { + return
; + } else { + return ( +
{ + item.onClick(); + onItemClicked?.(); + }} + > + {item.iconClassName != null ? ( + + ) : null} + {convertI18nText(item.text, t)} +
+ ); + } + })} +
+ ); +}; + +export default Menu; diff --git a/FrontEnd/src/views/common/menu/PopupMenu.tsx b/FrontEnd/src/views/common/menu/PopupMenu.tsx new file mode 100644 index 00000000..0d447f09 --- /dev/null +++ b/FrontEnd/src/views/common/menu/PopupMenu.tsx @@ -0,0 +1,17 @@ +import React from "react"; + +import Menu, { MenuItems } from "./Menu"; + +export interface PopupMenuProps { + items: MenuItems; + children: React.ReactElement; +} + +export const PopupMenu: React.FC = ({ items, children }) => { + const [show, setShow] = React.useState(false); + const toggle = (): void => setShow(!show); + + // TODO: + + return setShow(false)} />; +}; diff --git a/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx b/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx index 78057be2..78af1131 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx @@ -17,7 +17,7 @@ import CollapseButton from "./CollapseButton"; import { TimelineMemberDialog } from "./TimelineMember"; import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; import ConnectionStatusBadge from "./ConnectionStatusBadge"; -import { MenuItems, PopupMenu } from "../common/Menu"; +import { MenuItems, PopupMenu } from "../common/menu/Menu"; import FullPageDialog from "../common/dailog/FullPageDialog"; import Card from "../common/Card"; diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx index 13aacb54..1e6591c0 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx @@ -16,7 +16,7 @@ import { base64 } from "@/http/common"; import BlobImage from "../common/BlobImage"; import LoadingButton from "../common/button/LoadingButton"; -import { PopupMenu } from "../common/Menu"; +import { PopupMenu } from "../common/menu/Menu"; import Card from "../common/Card"; import MarkdownPostEdit from "./MarkdownPostEdit"; import TimelineLine from "./TimelineLine"; diff --git a/FrontEnd/src/views/timeline-common/index.css b/FrontEnd/src/views/timeline-common/index.css index b78564a3..70a1646d 100644 --- a/FrontEnd/src/views/timeline-common/index.css +++ b/FrontEnd/src/views/timeline-common/index.css @@ -13,19 +13,19 @@ @keyframes timeline-line-node-noncurrent { to { - box-shadow: 0 0 20px 3px var(--tl-primary-l1-color); + box-shadow: 0 0 20px 3px var(--cru-primary-l1-color); } } @keyframes timeline-line-node-current { to { - box-shadow: 0 0 20px 3px var(--tl-primary-enhance-l1-color); + box-shadow: 0 0 20px 3px var(--cru-primary-enhance-l1-color); } } @keyframes timeline-line-node-loading { to { - box-shadow: 0 0 20px 3px var(--tl-primary-l1-color); + box-shadow: 0 0 20px 3px var(--cru-primary-l1-color); } } @@ -79,7 +79,7 @@ .timeline-line .segment { width: 7px; - background: var(--tl-primary-color); + background: var(--cru-primary-color); } .timeline-line .segment.start { height: 1.8em; @@ -91,7 +91,7 @@ .timeline-line .segment.current-end { height: 2em; flex: 0 0 auto; - background: linear-gradient(var(--tl-primary-enhance-color), white); + background: linear-gradient(var(--cru-primary-enhance-color), white); } .timeline-line .node-container { flex: 0 0 auto; @@ -103,7 +103,7 @@ width: 20px; height: 20px; position: absolute; - background: var(--tl-primary-color); + background: var(--cru-primary-color); left: -1px; top: -1px; border-radius: 50%; @@ -113,7 +113,7 @@ animation-name: timeline-line-node-noncurrent; } .timeline-line .node-loading-edge { - color: var(--tl-primary-color); + color: var(--cru-primary-color); width: 38px; height: 38px; position: absolute; @@ -125,22 +125,22 @@ } .timeline-line.current .segment.start { background: linear-gradient( - var(--tl-primary-color), - var(--tl-primary-enhance-color) + var(--cru-primary-color), + var(--cru-primary-enhance-color) ); } .timeline-line.current .segment.end { - background: var(--tl-primary-enhance-color); + background: var(--cru-primary-enhance-color); } .timeline-line.current .node { - background: var(--tl-primary-enhance-color); + background: var(--cru-primary-enhance-color); animation-name: timeline-line-node-current; } .timeline-line.loading .node { - background: var(--tl-primary-color); + background: var(--cru-primary-color); animation-name: timeline-line-node-loading; } -- cgit v1.2.3 From 1d320f3e363d46900a420650360b68ba9a4f3f2f Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 30 Jun 2021 23:02:41 +0800 Subject: ... --- FrontEnd/src/views/common/Spinner.tsx | 12 ++- FrontEnd/src/views/common/button/Button.css | 99 ++++++++++++---------- FrontEnd/src/views/common/button/LoadingButton.tsx | 11 ++- FrontEnd/src/views/common/index.css | 8 ++ 4 files changed, 80 insertions(+), 50 deletions(-) (limited to 'FrontEnd/src/views/common/button') diff --git a/FrontEnd/src/views/common/Spinner.tsx b/FrontEnd/src/views/common/Spinner.tsx index b591d8ab..4c735fef 100644 --- a/FrontEnd/src/views/common/Spinner.tsx +++ b/FrontEnd/src/views/common/Spinner.tsx @@ -8,12 +8,14 @@ import "./Spinner.css"; export interface SpinnerProps { size?: "sm" | "md" | "lg" | number | string; color?: PaletteColorType; + className?: string; + style?: React.CSSProperties; } export default function Spinner( props: SpinnerProps ): React.ReactElement | null { - const { size, color } = props; + const { size, color, className, style } = props; const calculatedSize = size === "sm" ? "18px" @@ -30,8 +32,12 @@ export default function Spinner( return ( ); } diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css index 8b5b74a7..3e408d8d 100644 --- a/FrontEnd/src/views/common/button/Button.css +++ b/FrontEnd/src/views/common/button/Button.css @@ -1,72 +1,81 @@ -.cru-button { - color: white; - cursor: pointer; - padding: 0.2em 0.5em; - border-radius: 0.2em; - border: none; - transition: all 0.6s; -} - .cru-button.primary { - background-color: var(--cru-primary-color); -} - -.cru-button.primary:hover { - background-color: var(--cru-primary-f1-color); + --cru-button-color: var(--cru-primary-color); + --cru-button-f1-color: var(--cru-primary-f1-color); + --cru-button-f2-color: var(--cru-primary-f2-color); + --cru-button-f3-color: var(--cru-primary-f3-color); } -.cru-button.primary:active { - background-color: var(--cru-primary-f2-color); -} - -.cru-button.primary.disabled { - background-color: var(--cru-primary-f3-color); +.cru-button.primary-enhance { + --cru-button-color: var(--cru-primary-enhance-color); + --cru-button-f1-color: var(--cru-primary-enhance-f1-color); + --cru-button-f2-color: var(--cru-primary-enhance-f2-color); + --cru-button-f3-color: var(--cru-primary-enhance-f3-color); } .cru-button.secondary { - background-color: var(--cru-secondary-color); -} - -.cru-button.secondary:hover { - background-color: var(--cru-secondary-f1-color); + --cru-button-color: var(--cru-secondary-color); + --cru-button-f1-color: var(--cru-secondary-f1-color); + --cru-button-f2-color: var(--cru-secondary-f2-color); + --cru-button-f3-color: var(--cru-secondary-f3-color); } -.cru-button.secondary:active { - background-color: var(--cru-secondary-f2-color); +.cru-button.success { + --cru-button-color: var(--cru-success-color); + --cru-button-f1-color: var(--cru-success-f1-color); + --cru-button-f2-color: var(--cru-success-f2-color); + --cru-button-f3-color: var(--cru-success-f3-color); } -.cru-button.secondary.disabled { - background-color: var(--cru-secondary-f3-color); +.cru-button.danger { + --cru-button-color: var(--cru-danger-color); + --cru-button-f1-color: var(--cru-danger-f1-color); + --cru-button-f2-color: var(--cru-danger-f2-color); + --cru-button-f3-color: var(--cru-danger-f3-color); } -.cru-button.success { - background-color: var(--cru-success-color); +.cru-button:not(.outline) { + color: white; + cursor: pointer; + padding: 0.2em 0.5em; + border-radius: 0.2em; + border: none; + transition: all 0.5s; + background-color: var(--cru-button-color); } -.cru-button.success:hover { - background-color: var(--cru-success-f1-color); +.cru-button:not(.outline):hover { + background-color: var(--cru-button-f1-color); } -.cru-button.success:active { - background-color: var(--cru-success-f2-color); +.cru-button:not(.outline):active { + background-color: var(--cru-button-f2-color); } -.cru-button.success.disabled { - background-color: var(--cru-success-f3-color); +.cru-button:not(.outline):disabled { + background-color: var(--cru-button-f3-color); } -.cru-button.danger { - background-color: var(--cru-danger-color); +.cru-button.outline { + color: var(--cru-button-color); + border: var(--cru-button-color) 1px solid; + cursor: pointer; + padding: 0.2em 0.5em; + border-radius: 0.2em; + transition: all 0.6s; + background-color: white; } -.cru-button.danger:hover { - background-color: var(--cru-danger-f1-color); +.cru-button.outline:hover { + color: var(--cru-button-f1-color); + border-color: var(--cru-button-f1-color); } -.cru-button.danger:active { - background-color: var(--cru-danger-f2-color); +.cru-button.outline:active { + color: var(--cru-button-f2-color); + border-color: var(--cru-button-f2-color); } -.cru-button.danger.disabled { - background-color: var(--cru-danger-f3-color); +.cru-button.outline:disabled { + color: var(--cru-button-f3-color); + border-color: var(--cru-button-f3-color); } diff --git a/FrontEnd/src/views/common/button/LoadingButton.tsx b/FrontEnd/src/views/common/button/LoadingButton.tsx index aee83aa2..a7e34f91 100644 --- a/FrontEnd/src/views/common/button/LoadingButton.tsx +++ b/FrontEnd/src/views/common/button/LoadingButton.tsx @@ -11,9 +11,16 @@ const LoadingButton: React.FC<{ loading?: boolean } & CommonButtonProps> = ({ ...otherProps }) => { return ( - ); }; diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index 4a860e95..8eb24e1d 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -144,6 +144,14 @@ float: right; } +.cru-align-text-bottom { + vertical-align: text-bottom; +} + +.cru-align-middle { + vertical-align: middle; +} + .cru-clearfix::after { clear: both; } -- cgit v1.2.3 From 840f3bb6d5bcd69e7092373870ca9b9e2a12b1bf Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 30 Jun 2021 23:13:08 +0800 Subject: ... --- FrontEnd/src/palette.ts | 28 ++++----- FrontEnd/src/views/common/AppBar.css | 8 +-- FrontEnd/src/views/common/button/Button.css | 7 ++- .../src/views/common/dailog/FullPageDialog.css | 2 +- FrontEnd/src/views/common/index.css | 72 ++++++---------------- FrontEnd/src/views/common/menu/Menu.css | 2 +- 6 files changed, 46 insertions(+), 73 deletions(-) (limited to 'FrontEnd/src/views/common/button') diff --git a/FrontEnd/src/palette.ts b/FrontEnd/src/palette.ts index ca1f76cf..dedad5e2 100644 --- a/FrontEnd/src/palette.ts +++ b/FrontEnd/src/palette.ts @@ -27,6 +27,10 @@ export interface PaletteColor { r1: string; r2: string; r3: string; + t: string; + t1: string; + t2: string; + t3: string; [key: string]: string; } @@ -34,10 +38,6 @@ const paletteColorList = [ "primary", "primary-enhance", "secondary", - "text-primary", - "text-on-primary", - "text-on-primary-enhance", - "text-on-secondary", "danger", "success", ] as const; @@ -61,6 +61,12 @@ export function generatePaletteColor(color: string): PaletteColor { const r1 = light ? d1 : l1; const r2 = light ? d2 : l2; const r3 = light ? d3 : l3; + const _t = light ? Color("black") : Color("white"); + const t = _t.rgb().toString(); + const _b = light ? lightenBy : darkenBy; + const t1 = _b(_t, 0.1).rgb().toString(); + const t2 = _b(_t, 0.2).rgb().toString(); + const t3 = _b(_t, 0.3).rgb().toString(); return { color: c.rgb().toString(), @@ -76,6 +82,10 @@ export function generatePaletteColor(color: string): PaletteColor { r1, r2, r3, + t, + t1, + t2, + t3, }; } @@ -96,16 +106,6 @@ export function generatePalette(options: { primary: generatePaletteColor(p.toString()), "primary-enhance": generatePaletteColor(pe.toString()), secondary: generatePaletteColor(s.toString()), - "text-primary": generatePaletteColor("#111111"), - "text-on-primary": generatePaletteColor( - p.lightness() > 60 ? "black" : "white" - ), - "text-on-primary-enhance": generatePaletteColor( - pe.lightness() > 60 ? "black" : "white" - ), - "text-on-secondary": generatePaletteColor( - s.lightness() > 60 ? "black" : "white" - ), danger: generatePaletteColor("red"), success: generatePaletteColor("green"), }; diff --git a/FrontEnd/src/views/common/AppBar.css b/FrontEnd/src/views/common/AppBar.css index e4bdf852..3ec4fa36 100644 --- a/FrontEnd/src/views/common/AppBar.css +++ b/FrontEnd/src/views/common/AppBar.css @@ -16,16 +16,16 @@ } .app-bar a { - color: var(--cru-text-on-primary-r1-color); + color: var(--cru-primary-t1-color); text-decoration: none; margin: 0 1em; transition: color 1s; } .app-bar a:hover { - color: var(--cru-text-on-primary-color); + color: var(--cru-primary-t-color); } .app-bar a.active { - color: var(--cru-text-on-primary-color); + color: var(--cru-primary-t-color); } .app-bar-brand { @@ -89,7 +89,7 @@ margin-left: auto; font-size: 2em; margin-right: 1em; - color: var(--cru-text-on-primary-color); + color: var(--cru-primary-t-color); cursor: pointer; user-select: none; } diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css index 3e408d8d..11d211c5 100644 --- a/FrontEnd/src/views/common/button/Button.css +++ b/FrontEnd/src/views/common/button/Button.css @@ -1,5 +1,6 @@ .cru-button.primary { --cru-button-color: var(--cru-primary-color); + --cru-button-t-color: var(--cru-primary-t-color); --cru-button-f1-color: var(--cru-primary-f1-color); --cru-button-f2-color: var(--cru-primary-f2-color); --cru-button-f3-color: var(--cru-primary-f3-color); @@ -7,6 +8,7 @@ .cru-button.primary-enhance { --cru-button-color: var(--cru-primary-enhance-color); + --cru-button-t-color: var(--cru-primary-enhance-t-color); --cru-button-f1-color: var(--cru-primary-enhance-f1-color); --cru-button-f2-color: var(--cru-primary-enhance-f2-color); --cru-button-f3-color: var(--cru-primary-enhance-f3-color); @@ -14,6 +16,7 @@ .cru-button.secondary { --cru-button-color: var(--cru-secondary-color); + --cru-button-t-color: var(--cru-secondary-t-color); --cru-button-f1-color: var(--cru-secondary-f1-color); --cru-button-f2-color: var(--cru-secondary-f2-color); --cru-button-f3-color: var(--cru-secondary-f3-color); @@ -21,6 +24,7 @@ .cru-button.success { --cru-button-color: var(--cru-success-color); + --cru-button-t-color: var(--cru-success-t-color); --cru-button-f1-color: var(--cru-success-f1-color); --cru-button-f2-color: var(--cru-success-f2-color); --cru-button-f3-color: var(--cru-success-f3-color); @@ -28,13 +32,14 @@ .cru-button.danger { --cru-button-color: var(--cru-danger-color); + --cru-button-t-color: var(--cru-danger-t-color); --cru-button-f1-color: var(--cru-danger-f1-color); --cru-button-f2-color: var(--cru-danger-f2-color); --cru-button-f3-color: var(--cru-danger-f3-color); } .cru-button:not(.outline) { - color: white; + color: var(--cru-button-t-color); cursor: pointer; padding: 0.2em 0.5em; border-radius: 0.2em; diff --git a/FrontEnd/src/views/common/dailog/FullPageDialog.css b/FrontEnd/src/views/common/dailog/FullPageDialog.css index a7033398..a196981c 100644 --- a/FrontEnd/src/views/common/dailog/FullPageDialog.css +++ b/FrontEnd/src/views/common/dailog/FullPageDialog.css @@ -26,5 +26,5 @@ } .cru-full-page-back-button { - color: var(--cru-text-on-primary-color); + color: var(--cru-primary-t-color); } diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index 8eb24e1d..75ec6485 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -14,6 +14,10 @@ --cru-primary-r1-color: rgb(26, 136, 255); --cru-primary-r2-color: rgb(51, 149, 255); --cru-primary-r3-color: rgb(77, 163, 255); + --cru-primary-t-color: rgb(255, 255, 255); + --cru-primary-t1-color: rgb(230, 230, 230); + --cru-primary-t2-color: rgb(204, 204, 204); + --cru-primary-t3-color: rgb(179, 179, 179); --cru-primary-enhance-color: rgb(77, 163, 255); --cru-primary-enhance-l1-color: rgb(94, 172, 255); --cru-primary-enhance-l2-color: rgb(112, 181, 255); @@ -27,6 +31,10 @@ --cru-primary-enhance-r1-color: rgb(43, 145, 255); --cru-primary-enhance-r2-color: rgb(10, 128, 255); --cru-primary-enhance-r3-color: rgb(0, 112, 232); + --cru-primary-enhance-t-color: rgb(0, 0, 0); + --cru-primary-enhance-t1-color: rgb(26, 26, 26); + --cru-primary-enhance-t2-color: rgb(51, 51, 51); + --cru-primary-enhance-t3-color: rgb(77, 77, 77); --cru-secondary-color: rgb(128, 128, 128); --cru-secondary-l1-color: rgb(141, 141, 141); --cru-secondary-l2-color: rgb(153, 153, 153); @@ -40,58 +48,10 @@ --cru-secondary-r1-color: rgb(141, 141, 141); --cru-secondary-r2-color: rgb(153, 153, 153); --cru-secondary-r3-color: rgb(166, 166, 166); - --cru-text-primary-color: rgb(17, 17, 17); - --cru-text-primary-l1-color: rgb(41, 41, 41); - --cru-text-primary-l2-color: rgb(65, 65, 65); - --cru-text-primary-l3-color: rgb(88, 88, 88); - --cru-text-primary-d1-color: rgb(15, 15, 15); - --cru-text-primary-d2-color: rgb(14, 14, 14); - --cru-text-primary-d3-color: rgb(12, 12, 12); - --cru-text-primary-f1-color: rgb(15, 15, 15); - --cru-text-primary-f2-color: rgb(14, 14, 14); - --cru-text-primary-f3-color: rgb(12, 12, 12); - --cru-text-primary-r1-color: rgb(41, 41, 41); - --cru-text-primary-r2-color: rgb(65, 65, 65); - --cru-text-primary-r3-color: rgb(88, 88, 88); - --cru-text-on-primary-color: rgb(255, 255, 255); - --cru-text-on-primary-l1-color: rgb(255, 255, 255); - --cru-text-on-primary-l2-color: rgb(255, 255, 255); - --cru-text-on-primary-l3-color: rgb(255, 255, 255); - --cru-text-on-primary-d1-color: rgb(230, 230, 230); - --cru-text-on-primary-d2-color: rgb(204, 204, 204); - --cru-text-on-primary-d3-color: rgb(179, 179, 179); - --cru-text-on-primary-f1-color: rgb(255, 255, 255); - --cru-text-on-primary-f2-color: rgb(255, 255, 255); - --cru-text-on-primary-f3-color: rgb(255, 255, 255); - --cru-text-on-primary-r1-color: rgb(230, 230, 230); - --cru-text-on-primary-r2-color: rgb(204, 204, 204); - --cru-text-on-primary-r3-color: rgb(179, 179, 179); - --cru-text-on-primary-enhance-color: rgb(0, 0, 0); - --cru-text-on-primary-enhance-l1-color: rgb(26, 26, 26); - --cru-text-on-primary-enhance-l2-color: rgb(51, 51, 51); - --cru-text-on-primary-enhance-l3-color: rgb(77, 77, 77); - --cru-text-on-primary-enhance-d1-color: rgb(0, 0, 0); - --cru-text-on-primary-enhance-d2-color: rgb(0, 0, 0); - --cru-text-on-primary-enhance-d3-color: rgb(0, 0, 0); - --cru-text-on-primary-enhance-f1-color: rgb(0, 0, 0); - --cru-text-on-primary-enhance-f2-color: rgb(0, 0, 0); - --cru-text-on-primary-enhance-f3-color: rgb(0, 0, 0); - --cru-text-on-primary-enhance-r1-color: rgb(26, 26, 26); - --cru-text-on-primary-enhance-r2-color: rgb(51, 51, 51); - --cru-text-on-primary-enhance-r3-color: rgb(77, 77, 77); - --cru-text-on-secondary-color: rgb(255, 255, 255); - --cru-text-on-secondary-l1-color: rgb(255, 255, 255); - --cru-text-on-secondary-l2-color: rgb(255, 255, 255); - --cru-text-on-secondary-l3-color: rgb(255, 255, 255); - --cru-text-on-secondary-d1-color: rgb(230, 230, 230); - --cru-text-on-secondary-d2-color: rgb(204, 204, 204); - --cru-text-on-secondary-d3-color: rgb(179, 179, 179); - --cru-text-on-secondary-f1-color: rgb(255, 255, 255); - --cru-text-on-secondary-f2-color: rgb(255, 255, 255); - --cru-text-on-secondary-f3-color: rgb(255, 255, 255); - --cru-text-on-secondary-r1-color: rgb(230, 230, 230); - --cru-text-on-secondary-r2-color: rgb(204, 204, 204); - --cru-text-on-secondary-r3-color: rgb(179, 179, 179); + --cru-secondary-t-color: rgb(255, 255, 255); + --cru-secondary-t1-color: rgb(230, 230, 230); + --cru-secondary-t2-color: rgb(204, 204, 204); + --cru-secondary-t3-color: rgb(179, 179, 179); --cru-danger-color: rgb(255, 0, 0); --cru-danger-l1-color: rgb(255, 26, 26); --cru-danger-l2-color: rgb(255, 51, 51); @@ -105,6 +65,10 @@ --cru-danger-r1-color: rgb(255, 26, 26); --cru-danger-r2-color: rgb(255, 51, 51); --cru-danger-r3-color: rgb(255, 77, 77); + --cru-danger-t-color: rgb(255, 255, 255); + --cru-danger-t1-color: rgb(230, 230, 230); + --cru-danger-t2-color: rgb(204, 204, 204); + --cru-danger-t3-color: rgb(179, 179, 179); --cru-success-color: rgb(0, 128, 0); --cru-success-l1-color: rgb(0, 166, 0); --cru-success-l2-color: rgb(0, 204, 0); @@ -118,6 +82,10 @@ --cru-success-r1-color: rgb(0, 166, 0); --cru-success-r2-color: rgb(0, 204, 0); --cru-success-r3-color: rgb(0, 243, 0); + --cru-success-t-color: rgb(255, 255, 255); + --cru-success-t1-color: rgb(230, 230, 230); + --cru-success-t2-color: rgb(204, 204, 204); + --cru-success-t3-color: rgb(179, 179, 179); } .cru-color-primary { diff --git a/FrontEnd/src/views/common/menu/Menu.css b/FrontEnd/src/views/common/menu/Menu.css index e0ea2cad..c933b34f 100644 --- a/FrontEnd/src/views/common/menu/Menu.css +++ b/FrontEnd/src/views/common/menu/Menu.css @@ -13,7 +13,7 @@ } .cru-menu-item.color-primary:hover { - color: var(--cru-text-on-primary-color); + color: var(--cru-primary-t-color); background-color: var(--cru-primary-color); } -- cgit v1.2.3 From 4183e458436b0919385baaa50633d994daa407e1 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 30 Jun 2021 23:20:36 +0800 Subject: ... --- FrontEnd/src/views/common/button/Button.css | 5 ++++- FrontEnd/src/views/common/index.css | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'FrontEnd/src/views/common/button') diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css index 11d211c5..ec2dd798 100644 --- a/FrontEnd/src/views/common/button/Button.css +++ b/FrontEnd/src/views/common/button/Button.css @@ -67,20 +67,23 @@ padding: 0.2em 0.5em; border-radius: 0.2em; transition: all 0.6s; - background-color: white; + background-color: var(--cru-background-color); } .cru-button.outline:hover { color: var(--cru-button-f1-color); border-color: var(--cru-button-f1-color); + background-color: var(--cru-background-1-color); } .cru-button.outline:active { color: var(--cru-button-f2-color); border-color: var(--cru-button-f2-color); + background-color: var(--cru-background-2-color); } .cru-button.outline:disabled { color: var(--cru-button-f3-color); border-color: var(--cru-button-f3-color); + background-color: var(--cru-background-3-color); } diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index 75ec6485..02eed6d9 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -1,5 +1,8 @@ :root { --cru-background-color: #f8f9fa; + --cru-background-1-color: #e9ecef; + --cru-background-2-color: #dee2e6; + --cru-background-3-color: #ced4da; --cru-primary-color: rgb(0, 123, 255); --cru-primary-l1-color: rgb(26, 136, 255); -- cgit v1.2.3 From 81e54efaae0ec1d5f1f90c3ca40448f8c61f0e80 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 1 Jul 2021 16:43:59 +0800 Subject: ... --- FrontEnd/src/views/admin/UserAdmin.tsx | 12 ++++++------ FrontEnd/src/views/center/TimelineBoard.tsx | 6 +++--- FrontEnd/src/views/common/button/Button.css | 16 +++++++++------- FrontEnd/src/views/common/dailog/ConfirmDialog.tsx | 7 +++++-- FrontEnd/src/views/common/dailog/Dialog.css | 9 +++++++++ FrontEnd/src/views/common/dailog/OperationDialog.css | 0 FrontEnd/src/views/common/dailog/OperationDialog.tsx | 16 +++++++++++----- FrontEnd/src/views/common/index.css | 3 ++- FrontEnd/src/views/common/menu/Menu.css | 2 +- FrontEnd/src/views/login/index.tsx | 2 +- FrontEnd/src/views/settings/index.tsx | 11 ++++++----- FrontEnd/src/views/timeline-common/TimelinePostView.tsx | 2 +- 12 files changed, 54 insertions(+), 32 deletions(-) create mode 100644 FrontEnd/src/views/common/dailog/OperationDialog.css (limited to 'FrontEnd/src/views/common/button') diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx index 481db1cc..6d2760f2 100644 --- a/FrontEnd/src/views/admin/UserAdmin.tsx +++ b/FrontEnd/src/views/admin/UserAdmin.tsx @@ -206,23 +206,23 @@ const UserItem: React.FC = ({ user, on }) => { return (
setEditMaskVisible(true)} /> -

{user.username}

-
+

{user.username}

+
{t("admin:user.nickname")} {user.nickname}
-
+
{t("admin:user.uniqueId")} {user.uniqueId}
-
+
{t("admin:user.permissions")} {user.permissions.map((permission) => { return ( - + {permission}{" "} ); diff --git a/FrontEnd/src/views/center/TimelineBoard.tsx b/FrontEnd/src/views/center/TimelineBoard.tsx index 3961a7bc..8c1f5fac 100644 --- a/FrontEnd/src/views/center/TimelineBoard.tsx +++ b/FrontEnd/src/views/center/TimelineBoard.tsx @@ -48,16 +48,16 @@ const TimelineBoardItem: React.FC = ({ )} {title} - {name} + {name} {actions != null ? (
{ e.currentTarget.setPointerCapture(e.pointerId); actions.onMove.start(e); diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css index ec2dd798..54127f05 100644 --- a/FrontEnd/src/views/common/button/Button.css +++ b/FrontEnd/src/views/common/button/Button.css @@ -57,7 +57,8 @@ } .cru-button:not(.outline):disabled { - background-color: var(--cru-button-f3-color); + background-color: var(--cru-disable-color); + cursor: auto; } .cru-button.outline { @@ -67,23 +68,24 @@ padding: 0.2em 0.5em; border-radius: 0.2em; transition: all 0.6s; - background-color: var(--cru-background-color); + background-color: white; } .cru-button.outline:hover { color: var(--cru-button-f1-color); border-color: var(--cru-button-f1-color); - background-color: var(--cru-background-1-color); + background-color: var(--cru-background-color); } .cru-button.outline:active { color: var(--cru-button-f2-color); border-color: var(--cru-button-f2-color); - background-color: var(--cru-background-2-color); + background-color: var(--cru-background-1-color); } .cru-button.outline:disabled { - color: var(--cru-button-f3-color); - border-color: var(--cru-button-f3-color); - background-color: var(--cru-background-3-color); + color: var(--cru-disable-color); + border-color: var(--cru-disable-color); + background-color: white; + cursor: auto; } diff --git a/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx b/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx index 1ad52350..c10b1cdb 100644 --- a/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx +++ b/FrontEnd/src/views/common/dailog/ConfirmDialog.tsx @@ -16,12 +16,15 @@ const ConfirmDialog: React.FC<{ return ( -

{convertI18nText(title, t)}

+

{convertI18nText(title, t)}

+

{convertI18nText(body, t)}

-
+
+
-
+
+
@@ -446,11 +451,12 @@ const OperationDialog = <

{title}

+
{body}
); diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index 02eed6d9..62167cfc 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -2,7 +2,8 @@ --cru-background-color: #f8f9fa; --cru-background-1-color: #e9ecef; --cru-background-2-color: #dee2e6; - --cru-background-3-color: #ced4da; + + --cru-disable-color: #ced4da; --cru-primary-color: rgb(0, 123, 255); --cru-primary-l1-color: rgb(26, 136, 255); diff --git a/FrontEnd/src/views/common/menu/Menu.css b/FrontEnd/src/views/common/menu/Menu.css index c933b34f..a30eb5c6 100644 --- a/FrontEnd/src/views/common/menu/Menu.css +++ b/FrontEnd/src/views/common/menu/Menu.css @@ -22,7 +22,7 @@ } .cru-menu-item.color-secondary:hover { - color: var(--cru-text-on-secondary-color); + color: var(--cru-secondary-t-color); background-color: var(--cru-secondary-color); } diff --git a/FrontEnd/src/views/login/index.tsx b/FrontEnd/src/views/login/index.tsx index 6d70c64a..782acdaa 100644 --- a/FrontEnd/src/views/login/index.tsx +++ b/FrontEnd/src/views/login/index.tsx @@ -122,7 +122,7 @@ const LoginPage: React.FC = (_) => {
{error ?

{t(error)}

: null} -
+
{ diff --git a/FrontEnd/src/views/settings/index.tsx b/FrontEnd/src/views/settings/index.tsx index f25911d7..69a74327 100644 --- a/FrontEnd/src/views/settings/index.tsx +++ b/FrontEnd/src/views/settings/index.tsx @@ -28,7 +28,7 @@ const SettingsPage: React.FC = (_) => {
{user ? ( -

+

{t("settings.subheaders.account")}

{ {t("settings.changeNickname")}
setDialog("changepassword")} > {t("settings.changePassword")}
{ setDialog("logout"); }} @@ -60,13 +60,13 @@ const SettingsPage: React.FC = (_) => { ) : null} -

+

{t("settings.subheaders.customization")}

{t("settings.languagePrimary")}
- + {t("settings.languageSecondary")}
@@ -94,6 +94,7 @@ const SettingsPage: React.FC = (_) => { title="settings.dialogConfirmLogout.title" body="settings.dialogConfirmLogout.prompt" onClose={() => setDialog(null)} + open onConfirm={() => { void userService.logout().then(() => { history.push("/"); diff --git a/FrontEnd/src/views/timeline-common/TimelinePostView.tsx b/FrontEnd/src/views/timeline-common/TimelinePostView.tsx index 995c43df..652ff9c9 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostView.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePostView.tsx @@ -64,7 +64,7 @@ const TimelinePostView: React.FC = (props) => { > {post.editable ? ( { setOperationMaskVisible(true); e.stopPropagation(); -- cgit v1.2.3 From 47175cf9afcd86b0b92f7121ce0c3d85b948b6dd Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 1 Jul 2021 19:59:22 +0800 Subject: ... --- FrontEnd/src/services/alert.ts | 14 ++-- FrontEnd/src/views/admin/UserAdmin.tsx | 8 +- FrontEnd/src/views/common/alert/AlertHost.tsx | 45 +++++++---- FrontEnd/src/views/common/alert/alert.css | 28 +++++++ FrontEnd/src/views/common/button/Button.css | 60 +++----------- FrontEnd/src/views/common/button/FlatButton.css | 38 +-------- FrontEnd/src/views/common/button/TextButton.css | 36 --------- FrontEnd/src/views/common/button/TextButton.tsx | 18 ----- FrontEnd/src/views/common/button/common.ts | 6 +- FrontEnd/src/views/common/index.css | 100 ++++++++++++++++++++++++ FrontEnd/src/views/common/menu/Menu.css | 47 +---------- FrontEnd/src/views/common/menu/Menu.tsx | 2 +- 12 files changed, 189 insertions(+), 213 deletions(-) delete mode 100644 FrontEnd/src/views/common/button/TextButton.css delete mode 100644 FrontEnd/src/views/common/button/TextButton.tsx (limited to 'FrontEnd/src/views/common/button') diff --git a/FrontEnd/src/services/alert.ts b/FrontEnd/src/services/alert.ts index 48d482ea..abe56315 100644 --- a/FrontEnd/src/services/alert.ts +++ b/FrontEnd/src/services/alert.ts @@ -1,11 +1,13 @@ import React from "react"; import pull from "lodash/pull"; -import { BootstrapThemeColor, I18nText } from "@/common"; +import { I18nText } from "@/common"; +import { PaletteColorType } from "@/palette"; export interface AlertInfo { - type?: BootstrapThemeColor; - message: React.FC | I18nText; + type?: PaletteColorType; + message?: I18nText; + customMessage?: React.ReactElement; dismissTime?: number | "never"; } @@ -55,9 +57,3 @@ export const alertService = new AlertService(); export function pushAlert(alert: AlertInfo): void { alertService.push(alert); } - -export const kAlertHostId = "alert-host"; - -export function getAlertHost(): HTMLElement | null { - return document.getElementById(kAlertHostId); -} diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx index 6d2760f2..4ceff8ab 100644 --- a/FrontEnd/src/views/admin/UserAdmin.tsx +++ b/FrontEnd/src/views/admin/UserAdmin.tsx @@ -14,8 +14,8 @@ import { } from "@/http/user"; import { Trans, useTranslation } from "react-i18next"; import Button from "../common/button/Button"; -import TextButton from "../common/button/TextButton"; import Spinner from "../common/Spinner"; +import FlatButton from "../common/button/FlatButton"; interface DialogProps { open: boolean; @@ -232,12 +232,12 @@ const UserItem: React.FC = ({ user, on }) => { className={classnames("edit-mask", !editMaskVisible && "d-none")} onClick={() => setEditMaskVisible(false)} > - - + - void; @@ -51,14 +49,29 @@ export const AutoCloseAlert: React.FC = (props) => { }; return ( -
- {(() => { - const { message } = alert; - if (typeof message === "function") { - const Message = message; - return ; - } else return convertI18nText(message, t); - })()} +
+
+ {(() => { + const { message, customMessage } = alert; + if (customMessage != null) { + return customMessage; + } else { + return convertI18nText(message, t); + } + })()} +
+
+ +
); }; @@ -66,8 +79,6 @@ export const AutoCloseAlert: React.FC = (props) => { const AlertHost: React.FC = () => { const [alerts, setAlerts] = React.useState([]); - // react guarantee that state setters are stable, so we don't need to add it to dependency list - React.useEffect(() => { const consume = (alert: AlertInfoEx): void => { setAlerts((old) => [...old, alert]); @@ -80,7 +91,7 @@ const AlertHost: React.FC = () => { }, []); return ( -
+
{alerts.map((alert) => { return ( -): React.ReactElement | null { - return ( -