From 469ebd2ac9f6de17347fd6c47c68a7d28f0a50c1 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Tue, 15 Mar 2022 15:26:53 +0800 Subject: [PATCH] Recode attr input with text field component --- source/Component/AttrInput/AttrInput.scss | 121 +++++--------------- source/Component/AttrInput/AttrInput.tsx | 130 ++++++++++------------ source/Component/TextField/TextField.scss | 84 ++++++++++++++ source/Component/TextField/TextField.tsx | 104 +++++++++++++++++ source/Localization/EN-US.ts | 1 + source/Localization/ZH-CN.ts | 1 + 6 files changed, 277 insertions(+), 164 deletions(-) create mode 100644 source/Component/TextField/TextField.scss create mode 100644 source/Component/TextField/TextField.tsx diff --git a/source/Component/AttrInput/AttrInput.scss b/source/Component/AttrInput/AttrInput.scss index a680181..e16f206 100644 --- a/source/Component/AttrInput/AttrInput.scss +++ b/source/Component/AttrInput/AttrInput.scss @@ -1,120 +1,59 @@ @import "../Theme/Theme.scss"; -$line-min-height: 26px; +$line-min-height: 24px; -div.attr-input { - width: 100%; - display: flex; - min-height: $line-min-height; - padding: 5px 0; +div.attr-input-root { + display: flex; + justify-content: space-between; + align-items: center; - div.input-intro { - width: 50%; - height: 100%; - min-height: $line-min-height; - max-width: 220px; - display: flex; - align-items: center; - padding-right: 5px; - box-sizing: border-box; - } - - div.root-content { - width: 50%; + input { + display: block; + width: 100%; height: 100%; - max-width: 180px; + border: none; + outline: none; + background-color: transparent; min-height: $line-min-height; + }; - div.input-content { - box-sizing: border-box; - border: 1px solid transparent; - border-radius: 3px; - overflow: hidden; - display: flex; - justify-content: space-between; - align-items: center; - height: $line-min-height; - - input { - width: 100%; - height: 100%; - border: none; - outline:none; - }; - - input:focus { - border: none; - } - - div.button-left, div.button-right { - min-height: $line-min-height; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - vertical-align: middle; - cursor: pointer; - user-select: none; - padding: 0 3px; - } - } - - div.input-content.error { - border: 1px solid $lt-red; - } + input:focus { + border: none; + background-color: transparent; + } - div.input-content.focus { - border: 1px solid $lt-blue; - } - - div.err-message { - color: $lt-red; - padding-top: 5px; - min-height: $line-min-height; - } - - div.error-view { - border-radius: 3px; - overflow: hidden; - display: flex; - align-items: center; - height: $line-min-height; - text-overflow: ellipsis; - word-wrap: none; - word-break: keep-all; - white-space: nowrap; - cursor:not-allowed; - - span { - padding-left: 8px; - } - } + div.button-left, div.button-right { + min-height: $line-min-height; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + vertical-align: middle; + cursor: pointer; + user-select: none; + padding: 0 3px; } } -div.dark.attr-input { +div.dark.text-field-root { - div.input-content, div.error-view, - div.input-content input { + input { background-color: $lt-bg-color-lvl3-dark; color: $lt-font-color-normal-dark; } - div.error-view:hover, div.button-left:hover, div.button-right:hover { background-color: $lt-bg-color-lvl2-dark; } } -div.light.attr-input { +div.light.text-field-root { - div.input-content, div.error-view, - div.input-content input { + input { background-color: $lt-bg-color-lvl3-light; color: $lt-font-color-normal-light; } - div.error-view:hover, div.button-left:hover, div.button-right:hover { background-color: $lt-bg-color-lvl2-light; } diff --git a/source/Component/AttrInput/AttrInput.tsx b/source/Component/AttrInput/AttrInput.tsx index 7b9941a..eff6791 100644 --- a/source/Component/AttrInput/AttrInput.tsx +++ b/source/Component/AttrInput/AttrInput.tsx @@ -1,14 +1,12 @@ import { Component, ReactNode } from "react"; -import { FontLevel, Theme } from "@Component/Theme/Theme"; -import "./AttrInput.scss"; import { Icon } from "@fluentui/react"; import { Localization, AllI18nKeys } from "@Component/Localization/Localization"; import { ObjectID } from "@Model/Renderer"; +import { TextField, ITextFieldProps } from "../TextField/TextField"; +import "./AttrInput.scss"; -interface IAttrInputProps { +interface IAttrInputProps extends ITextFieldProps { id?: ObjectID; - keyI18n: AllI18nKeys; - infoI18n?: AllI18nKeys; value?: number | string; isNumber?: boolean; maxLength?: number; @@ -16,15 +14,14 @@ interface IAttrInputProps { max?: number; min?: number; step?: number; - disable?: boolean; - disableI18n?: AllI18nKeys; valueChange?: (value: this["isNumber"] extends true ? number : string) => any; } class AttrInput extends Component { private value: string = ""; - private error: ReactNode; + private error?: AllI18nKeys; + private errorOption?: Record; private numberTestReg = [/\.0*$/, /\.\d*[1-9]+0+$/]; private numberTester(value: string) { @@ -33,17 +30,21 @@ class AttrInput extends Component { this.numberTestReg[1].test(value); } - private check(value: string): ReactNode { + private check(value: string): AllI18nKeys | undefined { // 长度校验 const maxLength = this.props.maxLength ?? 32; if (value.length > maxLength) { - return + this.error = "Input.Error.Length"; + this.errorOption = { num: maxLength.toString() }; + return this.error; } const minLength = this.props.minLength ?? 1; if (value.length < minLength) { - return + this.error = "Input.Error.Length.Less"; + this.errorOption = { num: minLength.toString() }; + return this.error; } if (this.props.isNumber) { @@ -51,17 +52,22 @@ class AttrInput extends Component { // 数字校验 if (this.numberTester(value)) { - return + this.error = "Input.Error.Not.Number"; + return this.error; } // 最大值校验 if (this.props.max !== undefined && praseNumber > this.props.max) { - return + this.error = "Input.Error.Max"; + this.errorOption = { num: this.props.max.toString() }; + return this.error; } // 最小值校验 if (this.props.min !== undefined && praseNumber < this.props.min) { - return + this.error = "Input.Error.Min"; + this.errorOption = { num: this.props.min.toString() }; + return this.error; } } @@ -102,41 +108,33 @@ class AttrInput extends Component { private renderInput() { return <> -
- { - this.props.isNumber ?
this.changeValue(-1)} - > - -
: null - } - { - this.value = e.target.value; - this.error = this.check(e.target.value); - this.handelValueChange(); - }} - > - { - this.props.isNumber ?
this.changeValue(1)} - > - -
: null - } -
- { - this.error ? -
- {this.error} -
: null + { + this.props.isNumber ?
this.changeValue(-1)} + > + +
: null + } + { + this.value = e.target.value; + this.error = this.check(e.target.value); + this.handelValueChange(); + }} + > + { + this.props.isNumber ?
this.changeValue(1)} + > + +
: null } } @@ -166,33 +164,19 @@ class AttrInput extends Component { this.error = this.check(value.toString()); } - private renderErrorInput() { - return
- { - this.props.disableI18n ? - : - {this.props.value} - } -
- } - public render(): ReactNode { - return -
- -
-
- { - this.props.disable ? - this.renderErrorInput() : - this.renderInput() - } -
-
+ { + this.renderInput() + } + ; } } diff --git a/source/Component/TextField/TextField.scss b/source/Component/TextField/TextField.scss new file mode 100644 index 0000000..0f5cde7 --- /dev/null +++ b/source/Component/TextField/TextField.scss @@ -0,0 +1,84 @@ +@import "../Theme/Theme.scss"; + +$line-min-height: 26px; + +div.text-field-root { + min-height: $line-min-height; + width: 100%; + display: flex; + padding: 5px 0; + + div.text-field-intro { + min-height: $line-min-height; + width: 50%; + height: 100%; + max-width: 220px; + display: flex; + align-items: center; + padding-right: 5px; + box-sizing: border-box; + } + + div.text-field-container { + min-height: $line-min-height; + width: 50%; + height: 100%; + max-width: 180px; + + div.text-field-content { + min-height: $line-min-height; + width: 100%; + height: 100%; + } + + div.text-field-content-styled { + box-sizing: border-box; + border: 1px solid transparent; + border-radius: 3px; + overflow: hidden; + display: flex; + } + + div.text-field-content-disable { + align-items: center; + + span { + display: block; + padding-left: 8px; + } + } + + div.text-field-content-error { + border: 1px solid $lt-red; + } + + div.text-field-error-message { + padding-top: 5px; + color: $lt-red; + } + } +} + +div.dark.text-field-root { + + div.text-field-content-styled { + background-color: $lt-bg-color-lvl3-dark; + color: $lt-font-color-normal-dark; + } + + div.text-field-content-hover-styled:hover { + background-color: $lt-bg-color-lvl2-dark; + } +} + +div.light.text-field-root { + + div.text-field-content-styled { + background-color: $lt-bg-color-lvl3-light; + color: $lt-font-color-normal-light; + } + + div.text-field-content-hover-styled:hover { + background-color: $lt-bg-color-lvl2-light; + } +} \ No newline at end of file diff --git a/source/Component/TextField/TextField.tsx b/source/Component/TextField/TextField.tsx new file mode 100644 index 0000000..56508fb --- /dev/null +++ b/source/Component/TextField/TextField.tsx @@ -0,0 +1,104 @@ +import { Component, ReactNode, RefObject } from "react"; +import { FontLevel, Theme } from "@Component/Theme/Theme"; +import { AllI18nKeys, Localization } from "@Component/Localization/Localization"; +import "./TextField.scss"; + +interface ITextFieldProps { + className?: string; + keyI18n: AllI18nKeys; + infoI18n?: AllI18nKeys; + disableI18n?: AllI18nKeys; + disableI18nOption?: Record; + errorI18n?: AllI18nKeys; + errorI18nOption?: Record; + targetRef?: RefObject; + customStyle?: boolean; + customHoverStyle?: boolean; + onClick?: () => any; +} + +class TextField extends Component { + + private renderInput() { + + const classList: string[] = ["text-field-content"]; + if (this.props.className) { + classList.push(this.props.className); + } + if (!this.props.customStyle) { + classList.push("text-field-content-styled"); + } + if (!this.props.customHoverStyle) { + classList.push("text-field-content-hover-styled"); + } + if (this.props.errorI18n) { + classList.push("text-field-content-error"); + } + + return
+ { this.props.children } +
+ } + + private renderDisable() { + return
+ +
+ } + + private renderError() { + return
+ +
+ } + + public render(): ReactNode { + return <> + +
+ +
+
+ { + this.props.disableI18n ? + this.renderDisable() : + this.renderInput() + } + { + this.props.errorI18n ? + this.renderError() : + undefined + } +
+
+ + } +} + +export { TextField, ITextFieldProps }; \ No newline at end of file diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index b722ed6..013675c 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -44,6 +44,7 @@ const EN_US = { "Panel.Title.Group.Details.View": "Group", "Panel.Info.Group.Details.View": "Edit view group attributes", "Common.No.Data": "No Data", + "Common.No.Unknown.Error": "Unknown error", "Common.Attr.Title.Basic": "Basic properties", "Common.Attr.Title.Spatial": "Spatial property", "Common.Attr.Title.Individual.Generation": "Individual generation", diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index 991e55c..1207dbf 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -44,6 +44,7 @@ const ZH_CN = { "Panel.Title.Group.Details.View": "群", "Panel.Info.Group.Details.View": "编辑查看群属性", "Common.No.Data": "暂无数据", + "Common.No.Unknown.Error": "未知错误", "Common.Attr.Title.Basic": "基础属性", "Common.Attr.Title.Spatial": "空间属性", "Common.Attr.Title.Individual.Generation": "个体生成",