From 41e872d37677e8eba75a868f07205319889ffe9f Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 28 Apr 2022 14:25:09 +0800 Subject: ... --- .../src/views/common/dailog/OperationDialog.tsx | 4 +- FrontEnd/src/views/common/input/InputPanel.css | 25 +++ FrontEnd/src/views/common/input/InputPanel.tsx | 247 +++++++++++++++++++++ FrontEnd/src/views/register/index.tsx | 55 +++++ 4 files changed, 329 insertions(+), 2 deletions(-) create mode 100644 FrontEnd/src/views/common/input/InputPanel.css create mode 100644 FrontEnd/src/views/common/input/InputPanel.tsx create mode 100644 FrontEnd/src/views/register/index.tsx (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/views/common/dailog/OperationDialog.tsx b/FrontEnd/src/views/common/dailog/OperationDialog.tsx index 6bc846dd..b0ffdac9 100644 --- a/FrontEnd/src/views/common/dailog/OperationDialog.tsx +++ b/FrontEnd/src/views/common/dailog/OperationDialog.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { TwitterPicker } from "react-color"; +import classNames from "classnames"; import moment from "moment"; import { convertI18nText, I18nText, UiLogicError } from "@/common"; @@ -12,7 +13,6 @@ import LoadingButton from "../button/LoadingButton"; import Dialog from "./Dialog"; import "./OperationDialog.css"; -import classNames from "classnames"; interface DefaultErrorPromptProps { error?: string; @@ -42,7 +42,7 @@ export interface OperationDialogTextInput { initValue?: string; textFieldProps?: Omit< React.InputHTMLAttributes, - "type" | "value" | "onChange" | "aria-relevant" + "type" | "value" | "onChange" >; helperText?: string; } diff --git a/FrontEnd/src/views/common/input/InputPanel.css b/FrontEnd/src/views/common/input/InputPanel.css new file mode 100644 index 00000000..f9d6ac8b --- /dev/null +++ b/FrontEnd/src/views/common/input/InputPanel.css @@ -0,0 +1,25 @@ +.cru-input-panel-group { + display: block; + margin: 0.4em 0; +} + +.cru-input-panel-label { + display: block; + color: var(--cru-primary-color); +} + +.cru-input-panel-inline-label { + margin-inline-start: 0.5em; +} + +.cru-input-panel-error-text { + display: block; + font-size: 0.8em; + color: var(--cru-danger-color); +} + +.cru-input-panel-helper-text { + display: block; + font-size: 0.8em; + color: var(--cru-primary-color); +} diff --git a/FrontEnd/src/views/common/input/InputPanel.tsx b/FrontEnd/src/views/common/input/InputPanel.tsx new file mode 100644 index 00000000..1270cc53 --- /dev/null +++ b/FrontEnd/src/views/common/input/InputPanel.tsx @@ -0,0 +1,247 @@ +import React from "react"; +import classNames from "classnames"; +import { useTranslation } from "react-i18next"; +import { TwitterPicker } from "react-color"; + +import { convertI18nText, I18nText } from "@/common"; + +import "./InputPanel.css"; + +export interface TextInput { + type: "text"; + label?: I18nText; + helper?: I18nText; + password?: boolean; +} + +export interface BoolInput { + type: "bool"; + label: I18nText; + helper?: I18nText; +} + +export interface SelectInputOption { + value: string; + label: I18nText; + icon?: React.ReactElement; +} + +export interface SelectInput { + type: "select"; + label: I18nText; + options: SelectInputOption[]; +} + +export interface ColorInput { + type: "color"; + label?: I18nText; +} + +export interface DateTimeInput { + type: "datetime"; + label?: I18nText; + helper?: I18nText; +} + +export type Input = + | TextInput + | BoolInput + | SelectInput + | ColorInput + | DateTimeInput; + +interface InputTypeToValueTypeMap { + text: string; + bool: boolean; + select: string; + color: string; + datetime: string; +} + +type ValueTypes = InputTypeToValueTypeMap[keyof InputTypeToValueTypeMap]; + +type MapInputTypeToValueType = Type extends keyof InputTypeToValueTypeMap + ? InputTypeToValueTypeMap[Type] + : never; + +type MapInputToValueType = T extends Input + ? MapInputTypeToValueType + : T; + +type MapInputListToValueTypeList = { + [Index in keyof Tuple]: MapInputToValueType; +} & { length: Tuple["length"] }; + +export type OperationInputError = (I18nText | null | undefined)[]; + +export interface InputPanelProps { + scheme: InputList; + values: MapInputListToValueTypeList; + onChange: ( + values: MapInputListToValueTypeList, + index: number + ) => void; + error?: OperationInputError; + disable?: boolean; +} + +const InputPanel = ( + props: InputPanelProps +): React.ReactElement => { + const { values, onChange, scheme, error, disable } = props; + + const { t } = useTranslation(); + + const updateValue = (index: number, newValue: ValueTypes): void => { + const oldValues = values; + const newValues = oldValues.slice(); + newValues[index] = newValue; + onChange( + newValues as unknown as MapInputListToValueTypeList, + index + ); + }; + + return ( +
+ {scheme.map((item, index) => { + const v = values[index]; + const e: string | null = convertI18nText(error?.[index], t); + + if (item.type === "text") { + return ( +
+ {item.label && ( + + )} + { + const v = e.target.value; + updateValue(index, v); + }} + disabled={disable} + /> + {e &&
{e}
} + {item.helper && ( +
+ {convertI18nText(item.helper, t)} +
+ )} +
+ ); + } else if (item.type === "bool") { + return ( +
+ { + const value = event.currentTarget.checked; + updateValue(index, value); + }} + disabled={disable} + /> + + {e != null && ( +
{e}
+ )} + {item.helper && ( +
+ {convertI18nText(item.helper, t)} +
+ )} +
+ ); + } else if (item.type === "select") { + return ( +
+ + +
+ ); + } else if (item.type === "color") { + return ( +
+ + updateValue(index, result.hex)} + /> +
+ ); + } else if (item.type === "datetime") { + return ( +
+ {item.label && ( + + )} + { + const v = e.target.value; + updateValue(index, v); + }} + disabled={disable} + /> + {e != null && ( +
{e}
+ )} + {item.helper && ( +
+ {convertI18nText(item.helper, t)} +
+ )} +
+ ); + } + })} +
+ ); +}; + +export default InputPanel; diff --git a/FrontEnd/src/views/register/index.tsx b/FrontEnd/src/views/register/index.tsx new file mode 100644 index 00000000..da59ef94 --- /dev/null +++ b/FrontEnd/src/views/register/index.tsx @@ -0,0 +1,55 @@ +import React from "react"; + +const RegisterPage: React.FC = () => { + const [username, setUsername] = React.useState(""); + const [password, setPassword] = React.useState(""); + const [confirmPassword, setConfirmPassword] = React.useState(""); + const [registerCode, setRegisterCode] = React.useState(""); + + return ( +
+
+ + { + setUsername(e.target.value); + }} + /> +
+
+ + { + setPassword(e.target.value); + }} + /> +
+
+ + { + setConfirmPassword(e.target.value); + }} + /> +
+
+ + { + setRegisterCode(e.target.value); + }} + /> +
+
+ ); +}; + +export default RegisterPage; -- cgit v1.2.3