Detach form components to a separate directory
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
@import "../../Component/Theme/Theme.scss";
|
||||
|
||||
$line-min-height: 24px;
|
||||
|
||||
div.attr-input-root {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
min-height: $line-min-height;
|
||||
};
|
||||
|
||||
input:focus {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
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.text-field-root {
|
||||
|
||||
input {
|
||||
background-color: $lt-bg-color-lvl3-dark;
|
||||
color: $lt-font-color-normal-dark;
|
||||
}
|
||||
|
||||
div.button-left:hover, div.button-right:hover {
|
||||
background-color: $lt-bg-color-lvl2-dark;
|
||||
}
|
||||
}
|
||||
|
||||
div.light.text-field-root {
|
||||
|
||||
input {
|
||||
background-color: $lt-bg-color-lvl3-light;
|
||||
color: $lt-font-color-normal-light;
|
||||
}
|
||||
|
||||
div.button-left:hover, div.button-right:hover {
|
||||
background-color: $lt-bg-color-lvl2-light;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
import { Component, ReactNode } from "react";
|
||||
import { Icon } from "@fluentui/react";
|
||||
import { AllI18nKeys } from "@Component/Localization/Localization";
|
||||
import { ObjectID } from "@Model/Renderer";
|
||||
import { TextField, ITextFieldProps } from "@Input/TextField/TextField";
|
||||
import "./AttrInput.scss";
|
||||
|
||||
interface IAttrInputProps extends ITextFieldProps {
|
||||
id?: ObjectID;
|
||||
value?: number | string;
|
||||
isNumber?: boolean;
|
||||
maxLength?: number;
|
||||
minLength?: number;
|
||||
max?: number;
|
||||
min?: number;
|
||||
step?: number;
|
||||
valueChange?: (value: this["isNumber"] extends true ? number : string) => any;
|
||||
}
|
||||
|
||||
class AttrInput extends Component<IAttrInputProps> {
|
||||
|
||||
private value: string = "";
|
||||
private error?: AllI18nKeys;
|
||||
private errorOption?: Record<string, string>;
|
||||
private numberTestReg = [/\.0*$/, /\.\d*[1-9]+0+$/];
|
||||
|
||||
private numberTester(value: string) {
|
||||
return isNaN((value as any) / 1) ||
|
||||
this.numberTestReg[0].test(value) ||
|
||||
this.numberTestReg[1].test(value);
|
||||
}
|
||||
|
||||
private check(value: string): AllI18nKeys | undefined {
|
||||
|
||||
// 长度校验
|
||||
const maxLength = this.props.maxLength ?? 32;
|
||||
if (value.length > maxLength) {
|
||||
this.error = "Input.Error.Length";
|
||||
this.errorOption = { num: maxLength.toString() };
|
||||
return this.error;
|
||||
}
|
||||
|
||||
const minLength = this.props.minLength ?? 1;
|
||||
if (value.length < minLength) {
|
||||
this.error = "Input.Error.Length.Less";
|
||||
this.errorOption = { num: minLength.toString() };
|
||||
return this.error;
|
||||
}
|
||||
|
||||
if (this.props.isNumber) {
|
||||
const praseNumber = (value as any) / 1;
|
||||
|
||||
// 数字校验
|
||||
if (this.numberTester(value)) {
|
||||
this.error = "Input.Error.Not.Number";
|
||||
return this.error;
|
||||
}
|
||||
|
||||
// 最大值校验
|
||||
if (this.props.max !== undefined && praseNumber > this.props.max) {
|
||||
this.error = "Input.Error.Max";
|
||||
this.errorOption = { num: this.props.max.toString() };
|
||||
return this.error;
|
||||
}
|
||||
|
||||
// 最小值校验
|
||||
if (this.props.min !== undefined && praseNumber < this.props.min) {
|
||||
this.error = "Input.Error.Min";
|
||||
this.errorOption = { num: this.props.min.toString() };
|
||||
return this.error;
|
||||
}
|
||||
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private handelValueChange = () => {
|
||||
if (!this.error && this.props.valueChange) {
|
||||
if (this.props.isNumber) {
|
||||
let numberVal = (this.value as any) * 10000;
|
||||
this.value = (Math.round(numberVal) / 10000).toString();
|
||||
}
|
||||
this.props.valueChange(this.value);
|
||||
}
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
private changeValue = (direction: number) => {
|
||||
if (this.error) {
|
||||
return;
|
||||
} else {
|
||||
let newVal = (this.value as any / 1) + (this.props.step ?? 1) * direction;
|
||||
|
||||
// 最大值校验
|
||||
if (this.props.max !== undefined && newVal > this.props.max) {
|
||||
newVal = this.props.max;
|
||||
}
|
||||
|
||||
// 最小值校验
|
||||
if (this.props.min !== undefined && newVal < this.props.min) {
|
||||
newVal = this.props.min;
|
||||
}
|
||||
|
||||
this.value = newVal.toString();
|
||||
this.handelValueChange()
|
||||
}
|
||||
}
|
||||
|
||||
private renderInput() {
|
||||
return <>
|
||||
{
|
||||
this.props.isNumber ? <div
|
||||
className="button-left"
|
||||
onClick={() => this.changeValue(-1)}
|
||||
>
|
||||
<Icon iconName="ChevronLeft"></Icon>
|
||||
</div> : null
|
||||
}
|
||||
<input
|
||||
className="input"
|
||||
value={this.value}
|
||||
style={{
|
||||
padding: this.props.isNumber ? "0 3px" : "0 8px"
|
||||
}}
|
||||
onChange={(e) => {
|
||||
this.value = e.target.value;
|
||||
this.error = this.check(e.target.value);
|
||||
this.handelValueChange();
|
||||
}}
|
||||
></input>
|
||||
{
|
||||
this.props.isNumber ? <div
|
||||
className="button-right"
|
||||
onClick={() => this.changeValue(1)}
|
||||
>
|
||||
<Icon iconName="ChevronRight"></Icon>
|
||||
</div> : null
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: IAttrInputProps) {
|
||||
|
||||
// ID 都为空时更新
|
||||
if (!nextProps.id && !this.props.id) {
|
||||
this.updateValueFromProps(nextProps.value);
|
||||
}
|
||||
|
||||
// ID 变换时更新 State 到最新的 Props
|
||||
if (nextProps.id !== this.props.id) {
|
||||
this.updateValueFromProps(nextProps.value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public constructor(props: IAttrInputProps) {
|
||||
super(props);
|
||||
this.updateValueFromProps(props.value);
|
||||
}
|
||||
|
||||
private updateValueFromProps(val: IAttrInputProps["value"]) {
|
||||
const value = val ?? (this.props.isNumber ? "0" : "");
|
||||
this.value = value.toString();
|
||||
this.error = this.check(value.toString());
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
|
||||
return <TextField
|
||||
{...this.props}
|
||||
className="attr-input-root"
|
||||
customHoverStyle
|
||||
errorI18n={this.error}
|
||||
errorI18nOption={this.errorOption}
|
||||
>
|
||||
{
|
||||
this.renderInput()
|
||||
}
|
||||
</TextField>;
|
||||
}
|
||||
}
|
||||
|
||||
export { AttrInput };
|
||||
@@ -0,0 +1,101 @@
|
||||
@import "../../Component/Theme/Theme.scss";
|
||||
|
||||
div.behavior-picker-list {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 8px 0;
|
||||
|
||||
div.behavior-picker-line {
|
||||
width: 100%;
|
||||
padding: 0 !important;
|
||||
display: flex !important;
|
||||
justify-content: flex-start !important;
|
||||
|
||||
div.behavior-picker-line-color-view {
|
||||
width: 0;
|
||||
max-width: 0;
|
||||
height: 100%;
|
||||
|
||||
div {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-bottom: 10px solid transparent;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
div.behavior-picker-line-icon-view {
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
i.behavior-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
i.view-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
div.behavior-picker-line-icon-view:hover {
|
||||
|
||||
i.view-icon {
|
||||
color: $lt-green;
|
||||
}
|
||||
}
|
||||
|
||||
div.behavior-picker-title {
|
||||
height: 100%;
|
||||
width: calc(100% - 60px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
div {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
div.behavior-picker-title.is-deleted {
|
||||
|
||||
div {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
div.behavior-picker-title.behavior-add-line {
|
||||
width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
div.behavior-picker-line-delete-view {
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div.behavior-picker-line-delete-view:hover i {
|
||||
color: $lt-red;
|
||||
}
|
||||
}
|
||||
|
||||
div.behavior-picker-line:hover {
|
||||
|
||||
div.behavior-picker-line-icon-view {
|
||||
|
||||
i.behavior-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
i.view-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
import { Component, ReactNode, createRef } from "react";
|
||||
import { Icon } from "@fluentui/react";
|
||||
import { Behavior } from "@Model/Behavior";
|
||||
import { useSettingWithEvent, IMixinSettingProps } from "@Context/Setting";
|
||||
import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status";
|
||||
import { DetailsList } from "@Component/DetailsList/DetailsList";
|
||||
import { Localization } from "@Component/Localization/Localization";
|
||||
import { PickerList } from "@Input/PickerList/PickerList";
|
||||
import "./BehaviorPicker.scss";
|
||||
|
||||
interface IBehaviorPickerProps {
|
||||
behavior: Behavior[];
|
||||
focusBehavior?: Behavior;
|
||||
click?: (behavior: Behavior) => void;
|
||||
delete?: (behavior: Behavior) => void;
|
||||
action?: (behavior: Behavior) => void;
|
||||
add?: (behavior: Behavior) => void;
|
||||
}
|
||||
|
||||
interface IBehaviorPickerState {
|
||||
isPickerListOpen: boolean;
|
||||
}
|
||||
|
||||
@useStatusWithEvent("behaviorChange")
|
||||
@useSettingWithEvent("language")
|
||||
class BehaviorPicker extends Component<IBehaviorPickerProps & IMixinSettingProps & IMixinStatusProps> {
|
||||
|
||||
public state = {
|
||||
isPickerListOpen: false
|
||||
}
|
||||
|
||||
private isInnerClick: boolean = false;
|
||||
private clickLineRef = createRef<HTMLDivElement>();
|
||||
|
||||
private getData() {
|
||||
let data: Array<{select: boolean, key: string, behavior: Behavior | undefined}> = [];
|
||||
for (let i = 0; i < this.props.behavior.length; i++) {
|
||||
data.push({
|
||||
key: this.props.behavior[i].id,
|
||||
behavior: this.props.behavior[i],
|
||||
select: this.props.behavior[i].id === this.props.focusBehavior?.id
|
||||
})
|
||||
}
|
||||
data.push({
|
||||
key: "@@AddButton_List_Key",
|
||||
behavior: undefined,
|
||||
select: false
|
||||
})
|
||||
return data;
|
||||
}
|
||||
|
||||
private renderLine = (behavior?: Behavior): ReactNode => {
|
||||
if (behavior) {
|
||||
|
||||
const titleClassList: string[] = ["behavior-picker-title"];
|
||||
if (this.props.setting) {
|
||||
titleClassList.push(this.props.setting.language);
|
||||
}
|
||||
if (behavior.isDeleted()) {
|
||||
titleClassList.push("is-deleted");
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className="behavior-picker-line-color-view">
|
||||
<div style={{ borderLeft: `10px solid rgb(${behavior.color.join(",")})` }}/>
|
||||
</div>
|
||||
<div
|
||||
className="behavior-picker-line-icon-view"
|
||||
onClick={() => {
|
||||
this.isInnerClick = true;
|
||||
this.props.action && this.props.action(behavior);
|
||||
}}
|
||||
>
|
||||
{
|
||||
behavior.isDeleted() ?
|
||||
<Icon iconName={behavior.iconName}/>:
|
||||
<>
|
||||
<Icon iconName={behavior.iconName} className="behavior-icon"/>
|
||||
<Icon iconName="EditCreate" className="view-icon"/>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
<div className={titleClassList.join(" ")}>
|
||||
<div>{behavior.name}</div>
|
||||
</div>
|
||||
<div
|
||||
className="behavior-picker-line-delete-view"
|
||||
onClick={() => {
|
||||
this.isInnerClick = true;
|
||||
this.props.delete && this.props.delete(behavior);
|
||||
}}
|
||||
>
|
||||
<Icon iconName="Delete"/>
|
||||
</div>
|
||||
</>;
|
||||
} else {
|
||||
|
||||
const openPicker = () => {
|
||||
this.isInnerClick = true;
|
||||
this.setState({
|
||||
isPickerListOpen: true
|
||||
});
|
||||
}
|
||||
|
||||
return <>
|
||||
<div
|
||||
className="behavior-picker-line-icon-view"
|
||||
onClick={openPicker}
|
||||
>
|
||||
<Icon iconName="Add" className="add-icon"/>
|
||||
</div>
|
||||
<div
|
||||
className="behavior-picker-title behavior-add-line"
|
||||
onClick={openPicker}
|
||||
ref={this.clickLineRef}
|
||||
>
|
||||
<Localization i18nKey="Behavior.Picker.Add.Button"/>
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
private getAllData = (): Behavior[] => {
|
||||
if (this.props.status) {
|
||||
let res: Behavior[] = [];
|
||||
for (let i = 0; i < this.props.status.model.behaviorPool.length; i++) {
|
||||
|
||||
let isAdded = false;
|
||||
for (let j = 0; j < this.props.behavior.length; j++) {
|
||||
if (this.props.status.model.behaviorPool[i].id === this.props.behavior[j].id) {
|
||||
isAdded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAdded) {
|
||||
res.push(this.props.status.model.behaviorPool[i]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private renderPickerList(): ReactNode {
|
||||
return <PickerList
|
||||
objectList={this.getAllData()}
|
||||
noData="Behavior.Picker.Add.Nodata"
|
||||
target={this.clickLineRef}
|
||||
clickObjectItems={((item) => {
|
||||
if (item instanceof Behavior && this.props.add) {
|
||||
this.props.add(item);
|
||||
}
|
||||
this.setState({
|
||||
isPickerListOpen: false
|
||||
})
|
||||
})}
|
||||
dismiss={() => {
|
||||
this.setState({
|
||||
isPickerListOpen: false
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
return <>
|
||||
<DetailsList
|
||||
hideCheckBox
|
||||
className="behavior-picker-list"
|
||||
items={this.getData()}
|
||||
clickLine={(item) => {
|
||||
if (!this.isInnerClick && this.props.click && item.behavior) {
|
||||
this.props.click(item.behavior);
|
||||
}
|
||||
this.isInnerClick = false;
|
||||
}}
|
||||
columns={[{
|
||||
className: "behavior-picker-line",
|
||||
key: "behavior",
|
||||
render: this.renderLine
|
||||
}]}
|
||||
/>
|
||||
{this.state.isPickerListOpen ? this.renderPickerList() : null}
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
export { BehaviorPicker };
|
||||
@@ -0,0 +1,40 @@
|
||||
$line-min-height: 24px;
|
||||
|
||||
div.color-input {
|
||||
|
||||
div.color-view {
|
||||
width: $line-min-height;
|
||||
min-width: $line-min-height;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
div.color-box {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
div.value-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: $line-min-height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
div.text-box {
|
||||
max-width: calc( 100% - 24px);
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
padding-left: 1px;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.color-picker-root {
|
||||
width: 300px;
|
||||
height: 340px;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { Component, createRef, ReactNode } from "react";
|
||||
import { Callout, ColorPicker, DirectionalHint } from "@fluentui/react";
|
||||
import { TextField, ITextFieldProps } from "@Input/TextField/TextField";
|
||||
import "./ColorInput.scss";
|
||||
|
||||
interface IColorInputProps extends ITextFieldProps {
|
||||
value?: number[];
|
||||
normal?: boolean;
|
||||
valueChange?: (color: number[]) => any;
|
||||
}
|
||||
|
||||
interface IColorInputState {
|
||||
isPickerVisible: boolean;
|
||||
}
|
||||
|
||||
class ColorInput extends Component<IColorInputProps, IColorInputState> {
|
||||
|
||||
public constructor(props: IColorInputProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isPickerVisible: false
|
||||
}
|
||||
}
|
||||
|
||||
private pickerTarget = createRef<HTMLDivElement>();
|
||||
|
||||
private renderPicker() {
|
||||
return <Callout
|
||||
target={this.pickerTarget}
|
||||
directionalHint={DirectionalHint.topAutoEdge}
|
||||
onDismiss={() => {
|
||||
this.setState({
|
||||
isPickerVisible: false
|
||||
})
|
||||
}}
|
||||
>
|
||||
<div className="color-picker-root">
|
||||
<ColorPicker
|
||||
color={this.getColorString()}
|
||||
alphaType={"none"}
|
||||
onChange={(_, color) => {
|
||||
if (this.props.valueChange) {
|
||||
if (this.props.normal) {
|
||||
this.props.valueChange([
|
||||
color.r / 255,
|
||||
color.g / 255,
|
||||
color.b / 255,
|
||||
])
|
||||
} else {
|
||||
this.props.valueChange([
|
||||
color.r,
|
||||
color.g,
|
||||
color.b,
|
||||
])
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Callout>
|
||||
}
|
||||
|
||||
private renderColorInput() {
|
||||
return <>
|
||||
<div className="color-view">
|
||||
<div className="color-box" style={{
|
||||
backgroundColor: this.getColorString()
|
||||
}}/>
|
||||
</div>
|
||||
<div className="value-view">
|
||||
<div className="text-box">{this.getColorString(true)}</div>
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
|
||||
private getColorString(display?: boolean) {
|
||||
let color: number[] = this.props.value?.concat([]) ?? [0, 0, 0];
|
||||
if (this.props.normal) {
|
||||
color[0] = Math.round(color[0] * 255);
|
||||
color[1] = Math.round(color[1] * 255);
|
||||
color[2] = Math.round(color[2] * 255);
|
||||
}
|
||||
if (display) {
|
||||
return `rgb ( ${color[0] ?? 0}, ${color[1] ?? 0}, ${color[2] ?? 0} )`;
|
||||
} else {
|
||||
return `rgb(${color[0] ?? 0},${color[1] ?? 0},${color[2] ?? 0})`;
|
||||
}
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
return <>
|
||||
<TextField
|
||||
{...this.props}
|
||||
className="color-input"
|
||||
keyI18n={this.props.keyI18n}
|
||||
targetRef={this.pickerTarget}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
isPickerVisible: !this.props.disableI18n
|
||||
})
|
||||
}}
|
||||
>
|
||||
{ this.renderColorInput() }
|
||||
</TextField>
|
||||
{this.state.isPickerVisible ? this.renderPicker(): null}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
export { ColorInput };
|
||||
@@ -0,0 +1,38 @@
|
||||
@import "../../Component/Theme/Theme.scss";
|
||||
|
||||
$line-min-height: 24px;
|
||||
|
||||
div.combo-input {
|
||||
|
||||
div.value-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: $line-min-height;
|
||||
max-width: calc( 100% - 32px );
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 8px;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
span {
|
||||
word-break: keep-all;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
div.list-button {
|
||||
width: $line-min-height;
|
||||
height: $line-min-height;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { Component, createRef, ReactNode } from "react";
|
||||
import { Icon } from "@fluentui/react";
|
||||
import { PickerList, IDisplayItem } from "@Input/PickerList/PickerList";
|
||||
import { TextField, ITextFieldProps } from "@Input/TextField/TextField";
|
||||
import { Localization } from "@Component/Localization/Localization";
|
||||
import "./ComboInput.scss";
|
||||
interface IComboInputProps extends ITextFieldProps {
|
||||
allOption?: IDisplayItem[];
|
||||
value?: IDisplayItem;
|
||||
valueChange?: (value: IDisplayItem) => any;
|
||||
}
|
||||
|
||||
interface IComboInputState {
|
||||
isPickerVisible: boolean;
|
||||
}
|
||||
|
||||
class ComboInput extends Component<IComboInputProps, IComboInputState> {
|
||||
|
||||
public constructor(props: IComboInputProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isPickerVisible: false
|
||||
}
|
||||
}
|
||||
|
||||
private pickerTarget = createRef<HTMLDivElement>();
|
||||
|
||||
private renderPicker() {
|
||||
return <PickerList
|
||||
target={this.pickerTarget}
|
||||
displayItems={(this.props.allOption ?? []).map((item) => {
|
||||
return item.key === this.props.value?.key ?
|
||||
{...item, mark: true} : item;
|
||||
})}
|
||||
clickDisplayItems={((item) => {
|
||||
if (this.props.valueChange) {
|
||||
this.props.valueChange(item);
|
||||
}
|
||||
this.setState({
|
||||
isPickerVisible: false
|
||||
})
|
||||
})}
|
||||
dismiss={() => {
|
||||
this.setState({
|
||||
isPickerVisible: false
|
||||
})
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
return <>
|
||||
<TextField
|
||||
{...this.props}
|
||||
targetRef={this.pickerTarget}
|
||||
className="combo-input"
|
||||
keyI18n={this.props.keyI18n}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
isPickerVisible: true
|
||||
})
|
||||
}}
|
||||
>
|
||||
<div className="value-view">
|
||||
{
|
||||
this.props.value ?
|
||||
<Localization i18nKey={this.props.value.nameKey}/> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
<div className="list-button">
|
||||
<Icon iconName="ChevronDownMed"/>
|
||||
</div>
|
||||
</TextField>
|
||||
|
||||
{this.state.isPickerVisible ? this.renderPicker(): null}
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
export { ComboInput, IDisplayItem };
|
||||
@@ -0,0 +1,7 @@
|
||||
@import "../../Component/Theme/Theme.scss";
|
||||
|
||||
$line-min-height: 26px;
|
||||
|
||||
div.label-picker {
|
||||
min-height: $line-min-height;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Component, ReactNode, createRef } from "react";
|
||||
import { Label } from "@Model/Label";
|
||||
import { PickerList } from "@Input/PickerList/PickerList";
|
||||
import { TextField, ITextFieldProps } from "@Input/TextField/TextField";
|
||||
import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status";
|
||||
import { LabelList } from "@Component/LabelList/LabelList";
|
||||
import "./LabelPicker.scss"
|
||||
|
||||
interface ILabelPickerProps extends ITextFieldProps {
|
||||
labels: Label[];
|
||||
labelAdd?: (label: Label) => any;
|
||||
labelDelete?: (label: Label) => any;
|
||||
}
|
||||
|
||||
interface ILabelPickerState {
|
||||
isPickerVisible: boolean;
|
||||
}
|
||||
|
||||
@useStatusWithEvent("labelAttrChange", "labelChange")
|
||||
class LabelPicker extends Component<ILabelPickerProps & IMixinStatusProps, ILabelPickerState> {
|
||||
|
||||
public constructor(props: ILabelPickerProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isPickerVisible: false
|
||||
}
|
||||
}
|
||||
|
||||
private addButtonRef = createRef<HTMLDivElement>();
|
||||
|
||||
private getOtherLabel() {
|
||||
let res: Label[] = [];
|
||||
let nowLabel: Label[] = this.props.labels ?? [];
|
||||
if (this.props.status) {
|
||||
this.props.status.model.labelPool.forEach((aLabel) => {
|
||||
let isHas = false;
|
||||
nowLabel.forEach((nLabel) => {
|
||||
if (aLabel.equal(nLabel)) isHas = true;
|
||||
})
|
||||
if (!isHas) {
|
||||
res.push(aLabel);
|
||||
}
|
||||
})
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private renderPicker() {
|
||||
return <PickerList
|
||||
noData="Common.Attr.Key.Label.Picker.Nodata"
|
||||
objectList={this.getOtherLabel()}
|
||||
dismiss={() => {
|
||||
this.setState({
|
||||
isPickerVisible: false
|
||||
});
|
||||
}}
|
||||
clickObjectItems={(label) => {
|
||||
if (label instanceof Label && this.props.labelAdd) {
|
||||
this.props.labelAdd(label)
|
||||
}
|
||||
this.setState({
|
||||
isPickerVisible: false
|
||||
});
|
||||
}}
|
||||
target={this.addButtonRef}
|
||||
/>;
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
return <TextField
|
||||
{...this.props}
|
||||
className="label-picker"
|
||||
customHoverStyle
|
||||
customStyle
|
||||
keyI18n={this.props.keyI18n}
|
||||
>
|
||||
<LabelList
|
||||
addRef={this.addButtonRef}
|
||||
labels={this.props.labels}
|
||||
minHeight={26}
|
||||
deleteLabel={(label) => {
|
||||
this.props.labelDelete ? this.props.labelDelete(label) : 0;
|
||||
}}
|
||||
addLabel={() => {
|
||||
this.setState({
|
||||
isPickerVisible: true
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{this.state.isPickerVisible ? this.renderPicker(): null}
|
||||
</TextField>;
|
||||
}
|
||||
}
|
||||
|
||||
export { LabelPicker }
|
||||
@@ -0,0 +1,20 @@
|
||||
div.panel-error-message {
|
||||
transition: none !important;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
min-height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
div.panel-error-message.title {
|
||||
padding: 15px 0 5px 0;
|
||||
}
|
||||
|
||||
div.panel-error-message.title.first {
|
||||
padding: 5px 0 5px 0;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { FunctionComponent } from "react";
|
||||
import { AllI18nKeys, I18N } from "@Component/Localization/Localization";
|
||||
import { useSettingWithEvent, IMixinSettingProps, Themes, Language } from "@Context/Setting";
|
||||
import "./Message.scss";
|
||||
|
||||
interface IMessageProps {
|
||||
i18nKey?: AllI18nKeys;
|
||||
text?: string;
|
||||
options?: Record<string, string>;
|
||||
className?: string;
|
||||
isTitle?: boolean;
|
||||
first?: boolean;
|
||||
}
|
||||
|
||||
const MessageView: FunctionComponent<IMessageProps & IMixinSettingProps> = (props) => {
|
||||
|
||||
let theme = props.setting ? props.setting.themes : Themes.dark;
|
||||
let language: Language = props.setting ? props.setting.language : "EN_US";
|
||||
let classList: string[] = [
|
||||
"panel-error-message",
|
||||
theme === Themes.dark ? "dark" : "light",
|
||||
];
|
||||
|
||||
if (props.first) {
|
||||
classList.push("first");
|
||||
}
|
||||
|
||||
if (props.isTitle) {
|
||||
classList.push("title");
|
||||
classList.push("font-lvl3");
|
||||
}
|
||||
|
||||
if (props.className) {
|
||||
classList.push(props.className);
|
||||
}
|
||||
|
||||
return <div className={classList.join(" ")}>
|
||||
{
|
||||
props.text ?
|
||||
<span className={language}>{props.text}</span> :
|
||||
props.i18nKey ?
|
||||
<span className={language}>{
|
||||
I18N(language, props.i18nKey, props.options)
|
||||
}</span> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
const Message = useSettingWithEvent("language", "themes")(MessageView);
|
||||
export { Message };
|
||||
@@ -0,0 +1,58 @@
|
||||
@import "../../Component/Theme/Theme.scss";
|
||||
@import "../PickerList/RainbowBg.scss";
|
||||
|
||||
$line-min-height: 24px;
|
||||
|
||||
div.object-picker {
|
||||
|
||||
div.list-color {
|
||||
width: 3px;
|
||||
height: $line-min-height;
|
||||
border-radius: 1000px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div.value-view {
|
||||
flex-shrink: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: calc( 100% - 51px );
|
||||
min-height: $line-min-height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
word-break: keep-all;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
div.list-button {
|
||||
width: $line-min-height;
|
||||
height: $line-min-height;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div.list-empty {
|
||||
width: $line-min-height;
|
||||
height: $line-min-height;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div.list-empty:hover {
|
||||
color: $lt-red;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import { Component, createRef, ReactNode } from "react";
|
||||
import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status";
|
||||
import { Label } from "@Model/Label";
|
||||
import { Group } from "@Model/Group";
|
||||
import { Range } from "@Model/Range";
|
||||
import { CtrlObject } from "@Model/CtrlObject";
|
||||
import { Behavior } from "@Model/Behavior";
|
||||
import { TextField, ITextFieldProps } from "@Input/TextField/TextField";
|
||||
import { PickerList, IDisplayItem, getObjectDisplayInfo, IDisplayInfo } from "@Input/PickerList/PickerList";
|
||||
import { Localization } from "@Component/Localization/Localization";
|
||||
import { Icon } from "@fluentui/react";
|
||||
import "./ObjectPicker.scss";
|
||||
|
||||
type IObjectType = Label | Group | Range | CtrlObject;
|
||||
|
||||
interface IObjectPickerProps extends ITextFieldProps {
|
||||
type: string;
|
||||
value?: IObjectType;
|
||||
valueChange?: (value: IObjectType) => any;
|
||||
cleanValue?: () => any;
|
||||
}
|
||||
|
||||
interface IObjectPickerState {
|
||||
isPickerVisible: boolean;
|
||||
}
|
||||
|
||||
@useStatusWithEvent("objectChange", "labelChange")
|
||||
class ObjectPicker extends Component<IObjectPickerProps & IMixinStatusProps, IObjectPickerState> {
|
||||
|
||||
private getAllOption() {
|
||||
let option: Array<IObjectType> = [];
|
||||
if (!this.props.status) return option;
|
||||
|
||||
if (this.props.type.includes("L")) {
|
||||
for (let j = 0; j < this.props.status.model.labelPool.length; j++) {
|
||||
option.push(this.props.status.model.labelPool[j]);
|
||||
}
|
||||
|
||||
if (this.props.type.includes("R")) {
|
||||
option.push(this.props.status.model.allRangeLabel);
|
||||
}
|
||||
|
||||
if (this.props.type.includes("G")) {
|
||||
option.push(this.props.status.model.allGroupLabel);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.type.includes("R")) {
|
||||
for (let j = 0; j < this.props.status.model.objectPool.length; j++) {
|
||||
if (this.props.status.model.objectPool[j] instanceof Range) {
|
||||
option.push(this.props.status.model.objectPool[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.type.includes("G")) {
|
||||
for (let j = 0; j < this.props.status.model.objectPool.length; j++) {
|
||||
if (this.props.status.model.objectPool[j] instanceof Group) {
|
||||
option.push(this.props.status.model.objectPool[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
private isClean: boolean = false;
|
||||
|
||||
public constructor(props: IObjectPickerProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isPickerVisible: false
|
||||
}
|
||||
}
|
||||
|
||||
private pickerTarget = createRef<HTMLDivElement>();
|
||||
|
||||
private renderPicker() {
|
||||
return <PickerList
|
||||
noData="Object.Picker.List.No.Data"
|
||||
target={this.pickerTarget}
|
||||
objectList={this.getAllOption()}
|
||||
clickObjectItems={((item) => {
|
||||
if (item instanceof Behavior) return;
|
||||
if (this.props.valueChange) {
|
||||
this.props.valueChange(item);
|
||||
}
|
||||
this.setState({
|
||||
isPickerVisible: false
|
||||
})
|
||||
})}
|
||||
dismiss={() => {
|
||||
this.setState({
|
||||
isPickerVisible: false
|
||||
})
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
|
||||
let disPlayInfo: IDisplayInfo = getObjectDisplayInfo(this.props.value);
|
||||
let isDelete: boolean = !!this.props.value?.isDeleted();
|
||||
|
||||
return <>
|
||||
<TextField
|
||||
{...this.props}
|
||||
className="object-picker"
|
||||
keyI18n={this.props.keyI18n}
|
||||
targetRef={this.pickerTarget}
|
||||
onClick={() => {
|
||||
if (this.isClean) {
|
||||
this.isClean = false;
|
||||
} else {
|
||||
this.setState({
|
||||
isPickerVisible: true
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
"list-color" + (
|
||||
disPlayInfo.allLabel ? " rainbow-back-ground-color" : ""
|
||||
)
|
||||
}
|
||||
style={{
|
||||
backgroundColor: disPlayInfo.color
|
||||
}}
|
||||
/>
|
||||
<div className="list-button">
|
||||
<Icon iconName={disPlayInfo.icon}/>
|
||||
</div>
|
||||
<div
|
||||
className="value-view"
|
||||
style={{
|
||||
textDecoration: isDelete ? "line-through" : undefined,
|
||||
opacity: isDelete ? ".6" : undefined
|
||||
}}
|
||||
>
|
||||
{
|
||||
disPlayInfo.internal ?
|
||||
<Localization i18nKey={disPlayInfo.name as any}/> :
|
||||
<span>{disPlayInfo.name}</span>
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
className="list-empty"
|
||||
onClick={() => {
|
||||
if (this.props.cleanValue && disPlayInfo.name) {
|
||||
this.isClean = true;
|
||||
this.props.cleanValue();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{
|
||||
disPlayInfo.name ?
|
||||
<Icon iconName="delete"/> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</TextField>
|
||||
|
||||
{this.state.isPickerVisible ? this.renderPicker(): null}
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
export { ObjectPicker, IDisplayItem };
|
||||
@@ -0,0 +1,202 @@
|
||||
import { Component, Fragment, ReactNode } from "react";
|
||||
import { useSettingWithEvent, IMixinSettingProps, Language } from "@Context/Setting";
|
||||
import { AttrInput } from "@Input/AttrInput/AttrInput";
|
||||
import { ObjectID } from "@Model/Renderer";
|
||||
import { TogglesInput } from "@Input/TogglesInput/TogglesInput";
|
||||
import { ObjectPicker } from "@Input/ObjectPicker/ObjectPicker";
|
||||
import { AllI18nKeys } from "@Component/Localization/Localization";
|
||||
import { Message } from "@Input/Message/Message";
|
||||
import {
|
||||
IParameter, IParameterOption, IParameterOptionItem,
|
||||
IParameterValue, IParamValue, isObjectType, isVectorType
|
||||
} from "@Model/Parameter";
|
||||
import "./Parameter.scss";
|
||||
|
||||
interface IParameterProps<P extends IParameter = {}> {
|
||||
option: IParameterOption<P>;
|
||||
value: IParameterValue<P>;
|
||||
key: ObjectID;
|
||||
change: <K extends keyof P>(key: K, val: IParamValue<P[K]>) => any;
|
||||
i18n: <K extends keyof P>(option: IParameterOptionItem<P[K]>, language: Language) => string;
|
||||
title?: AllI18nKeys;
|
||||
titleOption?: Record<string, string>;
|
||||
isFirst?: boolean;
|
||||
}
|
||||
|
||||
@useSettingWithEvent("language")
|
||||
class Parameter<P extends IParameter> extends Component<IParameterProps<P> & IMixinSettingProps> {
|
||||
|
||||
private renderParameter<K extends keyof P>
|
||||
(key: K, option: IParameterOptionItem<P[K]>, value: IParamValue<P[K]>): ReactNode {
|
||||
|
||||
const indexKey = `${this.props.key}-${key}`;
|
||||
const type = option.type;
|
||||
const i18nString = this.props.i18n(option, this.props.setting?.language ?? "EN_US");
|
||||
|
||||
if (type === "number") {
|
||||
return <AttrInput
|
||||
key={indexKey}
|
||||
id={indexKey}
|
||||
keyI18n="Panel.Info.Behavior.Details.Parameter.Key"
|
||||
keyI18nOption={{ key: i18nString }}
|
||||
isNumber={true}
|
||||
step={option.numberStep}
|
||||
maxLength={option.maxLength}
|
||||
max={option.numberMax}
|
||||
min={option.numberMin}
|
||||
value={value as IParamValue<"number"> ?? 0}
|
||||
valueChange={(val) => {
|
||||
this.props.change(key, parseFloat(val) as IParamValue<P[K]>);
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
|
||||
else if (type === "string") {
|
||||
return <AttrInput
|
||||
key={indexKey}
|
||||
id={indexKey}
|
||||
keyI18n="Panel.Info.Behavior.Details.Parameter.Key"
|
||||
keyI18nOption={{ key: i18nString }}
|
||||
maxLength={option.maxLength}
|
||||
value={value as IParamValue<"string"> ?? ""}
|
||||
valueChange={(val) => {
|
||||
this.props.change(key, val as IParamValue<P[K]>);
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
|
||||
else if (type === "boolean") {
|
||||
return <TogglesInput
|
||||
key={indexKey}
|
||||
keyI18n="Panel.Info.Behavior.Details.Parameter.Key"
|
||||
keyI18nOption={{ key: i18nString }}
|
||||
onIconName={option.iconName}
|
||||
value={value as IParamValue<"boolean"> ?? false}
|
||||
valueChange={(val) => {
|
||||
this.props.change(key, val as IParamValue<P[K]>);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
else if (isObjectType(type)) {
|
||||
|
||||
type IObjectParamValue = IParamValue<"G" | "R" | "LG" | "LR">;
|
||||
const typedValue = value as IObjectParamValue;
|
||||
|
||||
return <ObjectPicker
|
||||
key={indexKey}
|
||||
keyI18n="Panel.Info.Behavior.Details.Parameter.Key"
|
||||
keyI18nOption={{ key: i18nString }}
|
||||
type={type}
|
||||
value={typedValue.picker}
|
||||
valueChange={(obj) => {
|
||||
typedValue.picker = obj as IObjectParamValue["picker"];
|
||||
this.props.change(key, typedValue as IParamValue<P[K]>);
|
||||
}}
|
||||
cleanValue={() => {
|
||||
typedValue.picker = undefined as IObjectParamValue["picker"];
|
||||
this.props.change(key, typedValue as IParamValue<P[K]>);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
else if (isVectorType(type)) {
|
||||
|
||||
type IObjectParamValue = IParamValue<"vec">;
|
||||
const typedValue = value as IObjectParamValue;
|
||||
|
||||
return <Fragment key={indexKey}>
|
||||
|
||||
<AttrInput
|
||||
key={`${indexKey}-X`}
|
||||
id={indexKey}
|
||||
keyI18n="Panel.Info.Behavior.Details.Parameter.Key.Vec.X"
|
||||
keyI18nOption={{ key: i18nString }}
|
||||
isNumber={true}
|
||||
step={option.numberStep}
|
||||
maxLength={option.maxLength}
|
||||
max={option.numberMax}
|
||||
min={option.numberMin}
|
||||
value={typedValue[0] ?? 0}
|
||||
valueChange={(val) => {
|
||||
typedValue[0] = parseFloat(val);
|
||||
this.props.change(key, typedValue as IParamValue<P[K]>);
|
||||
}}
|
||||
/>
|
||||
|
||||
<AttrInput
|
||||
key={`${indexKey}-Y`}
|
||||
id={indexKey}
|
||||
keyI18n="Panel.Info.Behavior.Details.Parameter.Key.Vec.Y"
|
||||
keyI18nOption={{ key: i18nString }}
|
||||
isNumber={true}
|
||||
step={option.numberStep}
|
||||
maxLength={option.maxLength}
|
||||
max={option.numberMax}
|
||||
min={option.numberMin}
|
||||
value={typedValue[1] ?? 0}
|
||||
valueChange={(val) => {
|
||||
typedValue[1] = parseFloat(val);
|
||||
this.props.change(key, typedValue as IParamValue<P[K]>);
|
||||
}}
|
||||
/>
|
||||
|
||||
<AttrInput
|
||||
key={`${indexKey}-Z`}
|
||||
id={indexKey}
|
||||
keyI18n="Panel.Info.Behavior.Details.Parameter.Key.Vec.Z"
|
||||
keyI18nOption={{ key: i18nString }}
|
||||
isNumber={true}
|
||||
step={option.numberStep}
|
||||
maxLength={option.maxLength}
|
||||
max={option.numberMax}
|
||||
min={option.numberMin}
|
||||
value={typedValue[2] ?? 0}
|
||||
valueChange={(val) => {
|
||||
typedValue[2] = parseFloat(val);
|
||||
this.props.change(key, typedValue as IParamValue<P[K]>);
|
||||
}}
|
||||
/>
|
||||
|
||||
</Fragment>
|
||||
}
|
||||
|
||||
else {
|
||||
return <Fragment key={indexKey}/>
|
||||
}
|
||||
}
|
||||
|
||||
private renderAllParameter(key: Array<keyof P>) {
|
||||
return key.map((key) => {
|
||||
return this.renderParameter(
|
||||
key,
|
||||
this.props.option[key],
|
||||
this.props.value[key],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
const allOptionKeys: Array<keyof P> = Object.getOwnPropertyNames(this.props.option);
|
||||
|
||||
return <>
|
||||
|
||||
{
|
||||
allOptionKeys.length <= 0 && this.props.title ?
|
||||
<Message
|
||||
isTitle
|
||||
first={this.props.isFirst}
|
||||
i18nKey={this.props.title}
|
||||
options={this.props.titleOption}
|
||||
/> : null
|
||||
}
|
||||
|
||||
{
|
||||
this.renderAllParameter(allOptionKeys)
|
||||
}
|
||||
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
export { Parameter }
|
||||
@@ -0,0 +1,78 @@
|
||||
@import "./RainbowBg.scss";
|
||||
|
||||
div.picker-list-root {
|
||||
min-width: 200px;
|
||||
height: 100%;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
overflow-y: auto;
|
||||
|
||||
div.picker-list-item {
|
||||
min-width: 200px;
|
||||
padding-right: 10px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
|
||||
div.list-item-color {
|
||||
width: 3px;
|
||||
height: calc( 100% - 6px );
|
||||
margin: 3px 0 3px 3px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 1000px;
|
||||
overflow: hidden;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
div.list-item-icon {
|
||||
width: 30px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div.list-item-name {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
div.picker-list-item:hover {
|
||||
background-color: rgba($color: #000000, $alpha: .1);
|
||||
}
|
||||
|
||||
span.picker-list-nodata {
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
div.picker-list-root::-webkit-scrollbar {
|
||||
width : 0; /*高宽分别对应横竖滚动条的尺寸*/
|
||||
height: 0;
|
||||
}
|
||||
|
||||
div.picker-list-root::-webkit-scrollbar {
|
||||
width : 8px; /*高宽分别对应横竖滚动条的尺寸*/
|
||||
height: 0;
|
||||
}
|
||||
|
||||
div.picker-list-root::-webkit-scrollbar-thumb {
|
||||
/*滚动条里面小方块*/
|
||||
border-radius: 8px;
|
||||
background-color: rgba($color: #000000, $alpha: .2);
|
||||
}
|
||||
|
||||
div.picker-list-root::-webkit-scrollbar-track {
|
||||
/*滚动条里面轨道*/
|
||||
border-radius: 8px;
|
||||
background-color: rgba($color: #000000, $alpha: 0);
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import { AllI18nKeys, Localization } from "@Component/Localization/Localization";
|
||||
import { Callout, DirectionalHint, Icon } from "@fluentui/react";
|
||||
import { Behavior } from "@Model/Behavior";
|
||||
import { CtrlObject } from "@Model/CtrlObject";
|
||||
import { Group } from "@Model/Group";
|
||||
import { Label } from "@Model/Label";
|
||||
import { Range } from "@Model/Range";
|
||||
import { Component, ReactNode, RefObject } from "react";
|
||||
import "./PickerList.scss";
|
||||
|
||||
type IPickerListItem = CtrlObject | Label | Range | Group | Behavior;
|
||||
interface IDisplayInfo {
|
||||
color: string;
|
||||
icon: string;
|
||||
name: string;
|
||||
internal: boolean;
|
||||
allLabel: boolean;
|
||||
};
|
||||
|
||||
interface IDisplayItem {
|
||||
nameKey: AllI18nKeys;
|
||||
key: string;
|
||||
mark?: boolean;
|
||||
}
|
||||
|
||||
function getObjectDisplayInfo(item?: IPickerListItem): IDisplayInfo {
|
||||
|
||||
if (!item) {
|
||||
return {
|
||||
color: "transparent",
|
||||
icon: "Label",
|
||||
name: "Input.Error.Select",
|
||||
internal: true,
|
||||
allLabel: false
|
||||
}
|
||||
}
|
||||
|
||||
let color: number[] | string = [];
|
||||
let icon: string = "tag";
|
||||
let name: string = "";
|
||||
let internal: boolean = false;
|
||||
let allLabel: boolean = false;
|
||||
|
||||
if (item instanceof Range) {
|
||||
icon = "CubeShape"
|
||||
}
|
||||
if (item instanceof Group) {
|
||||
icon = "WebAppBuilderFragment"
|
||||
}
|
||||
if (item instanceof CtrlObject) {
|
||||
color = [];
|
||||
color[0] = Math.round(item.color[0] * 255);
|
||||
color[1] = Math.round(item.color[1] * 255);
|
||||
color[2] = Math.round(item.color[2] * 255);
|
||||
name = item.displayName;
|
||||
}
|
||||
if (item instanceof Label) {
|
||||
|
||||
if (item.isBuildIn) {
|
||||
internal = true;
|
||||
allLabel = true;
|
||||
color = "transparent";
|
||||
if (item.id === "AllRange") {
|
||||
icon = "ProductList";
|
||||
name = "Build.In.Label.Name.All.Range";
|
||||
} else if (item.id === "AllGroup") {
|
||||
icon = "SizeLegacy";
|
||||
name = "Build.In.Label.Name.All.Group";
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
icon = "tag";
|
||||
color = item.color.concat([]);
|
||||
name = item.name;
|
||||
}
|
||||
}
|
||||
|
||||
if (item instanceof Behavior) {
|
||||
color = item.color;
|
||||
icon = item.iconName;
|
||||
name = item.name;
|
||||
internal = false;
|
||||
allLabel = false;
|
||||
}
|
||||
|
||||
if (Array.isArray(color)) {
|
||||
color = `rgb(${color[0]},${color[1]},${color[2]})`;
|
||||
}
|
||||
|
||||
return {
|
||||
color: color,
|
||||
icon: icon,
|
||||
name: name,
|
||||
internal: internal,
|
||||
allLabel: allLabel
|
||||
}
|
||||
}
|
||||
|
||||
interface IPickerListProps {
|
||||
displayItems?: IDisplayItem[];
|
||||
objectList?: IPickerListItem[];
|
||||
target?: RefObject<any>;
|
||||
noData?: AllI18nKeys;
|
||||
dismiss?: () => any;
|
||||
clickObjectItems?: (item: IPickerListItem) => any;
|
||||
clickDisplayItems?: (item: IDisplayItem) => any;
|
||||
}
|
||||
|
||||
class PickerList extends Component<IPickerListProps> {
|
||||
|
||||
private renderItem(item: IPickerListItem) {
|
||||
const displayInfo = getObjectDisplayInfo(item);
|
||||
|
||||
return <div
|
||||
className="picker-list-item"
|
||||
key={item.id}
|
||||
onClick={() => {
|
||||
if (this.props.clickObjectItems) {
|
||||
this.props.clickObjectItems(item)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={
|
||||
"list-item-color" + (
|
||||
displayInfo.allLabel ? " rainbow-back-ground-color" : ""
|
||||
)
|
||||
}
|
||||
style={{
|
||||
backgroundColor: displayInfo.color
|
||||
}}
|
||||
></div>
|
||||
<div className="list-item-icon">
|
||||
<Icon iconName={displayInfo.icon}/>
|
||||
</div>
|
||||
<div className="list-item-name">
|
||||
{
|
||||
displayInfo.internal ?
|
||||
<Localization i18nKey={displayInfo.name}/> :
|
||||
displayInfo.name
|
||||
}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
private renderString(item: IDisplayItem) {
|
||||
return <div
|
||||
className="picker-list-item"
|
||||
key={item.key}
|
||||
onClick={() => {
|
||||
if (this.props.clickDisplayItems) {
|
||||
this.props.clickDisplayItems(item)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="list-item-icon">
|
||||
<Icon iconName="CheckMark" style={{
|
||||
display: item.mark ? "block" : "none"
|
||||
}}/>
|
||||
</div>
|
||||
<div className="list-item-name">
|
||||
<Localization i18nKey={item.nameKey}/>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
return <Callout
|
||||
onDismiss={this.props.dismiss}
|
||||
target={this.props.target}
|
||||
directionalHint={DirectionalHint.topCenter}
|
||||
>
|
||||
<div className="picker-list-root">
|
||||
{this.props.objectList ? this.props.objectList.map((item) => {
|
||||
return this.renderItem(item);
|
||||
}) : null}
|
||||
{this.props.displayItems ? this.props.displayItems.map((item) => {
|
||||
return this.renderString(item);
|
||||
}) : null}
|
||||
{
|
||||
!(this.props.objectList || this.props.displayItems) ||
|
||||
!(
|
||||
this.props.objectList && this.props.objectList.length > 0 ||
|
||||
this.props.displayItems && this.props.displayItems.length > 0
|
||||
) ?
|
||||
<Localization
|
||||
className="picker-list-nodata"
|
||||
i18nKey={this.props.noData ?? "Common.No.Data"}
|
||||
/>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
</Callout>
|
||||
}
|
||||
}
|
||||
|
||||
export { PickerList, IDisplayItem, IDisplayInfo, getObjectDisplayInfo }
|
||||
@@ -0,0 +1,13 @@
|
||||
div.rainbow-back-ground-color {
|
||||
background-image: linear-gradient(
|
||||
to top,
|
||||
orangered,
|
||||
orange,
|
||||
gold,
|
||||
lightgreen,
|
||||
cyan,
|
||||
dodgerblue,
|
||||
mediumpurple,
|
||||
hotpink,
|
||||
orangered);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
@import "../../Component/Theme/Theme.scss";
|
||||
|
||||
$search-box-height: 26px;
|
||||
|
||||
div.search-box-root {
|
||||
min-height: $search-box-height;
|
||||
max-width: 280px;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
|
||||
div.search-icon {
|
||||
min-width: $search-box-height;
|
||||
height: $search-box-height;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
div.input-box {
|
||||
width: calc(100% - 26px);
|
||||
height: $search-box-height;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
div.clean-box {
|
||||
height: $search-box-height;
|
||||
width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
div.clean-box-view {
|
||||
flex-shrink: 0;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
right: 24px;
|
||||
border-radius: 3px;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.dark.search-box-root {
|
||||
|
||||
div.clean-box {
|
||||
|
||||
div.clean-box-view:hover {
|
||||
background-color: $lt-bg-color-lvl2-dark;
|
||||
}
|
||||
|
||||
div.clean-box-view {
|
||||
background-color: $lt-bg-color-lvl3-dark;
|
||||
}
|
||||
}
|
||||
|
||||
div.input-box input {
|
||||
color: $lt-font-color-normal-dark;
|
||||
}
|
||||
}
|
||||
|
||||
div.light.search-box-root {
|
||||
|
||||
div.clean-box {
|
||||
|
||||
div.clean-box-view:hover {
|
||||
background-color: $lt-bg-color-lvl3-light;
|
||||
}
|
||||
|
||||
div.clean-box-view {
|
||||
background-color: $lt-bg-color-lvl2-light;
|
||||
}
|
||||
}
|
||||
|
||||
div.input-box input {
|
||||
color: $lt-font-color-normal-light;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Component, ReactNode } from "react";
|
||||
import { Icon } from "@fluentui/react";
|
||||
import { AllI18nKeys, I18N } from "@Component/Localization/Localization";
|
||||
import { BackgroundLevel, FontLevel, Theme } from "@Component/Theme/Theme";
|
||||
import { useSettingWithEvent, IMixinSettingProps } from "@Context/Setting";
|
||||
import "./SearchBox.scss";
|
||||
|
||||
interface ISearchBoxProps {
|
||||
value?: string;
|
||||
valueChange?: (value: string) => void;
|
||||
placeholderI18N?: AllI18nKeys;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@useSettingWithEvent("language")
|
||||
class SearchBox extends Component<ISearchBoxProps & IMixinSettingProps> {
|
||||
|
||||
private renderCleanBox() {
|
||||
return <div className="clean-box">
|
||||
<div
|
||||
className="clean-box-view"
|
||||
onClick={() => {
|
||||
if (this.props.valueChange) {
|
||||
this.props.valueChange("")
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon iconName="CalculatorMultiply"/>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
return <Theme
|
||||
className={"search-box-root" + (this.props.className ? ` ${this.props.className}` : "")}
|
||||
backgroundLevel={BackgroundLevel.Level3}
|
||||
fontLevel={FontLevel.normal}
|
||||
>
|
||||
<div className="search-icon">
|
||||
<Icon iconName="search"/>
|
||||
</div>
|
||||
<div className="input-box">
|
||||
<input
|
||||
value={this.props.value}
|
||||
placeholder={
|
||||
I18N(this.props, this.props.placeholderI18N ?? "Common.Search.Placeholder")
|
||||
}
|
||||
onInput={(e) => {
|
||||
if (e.target instanceof HTMLInputElement && this.props.valueChange) {
|
||||
this.props.valueChange(e.target.value)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{this.props.value ? this.renderCleanBox() : null}
|
||||
</Theme>
|
||||
}
|
||||
}
|
||||
|
||||
export { SearchBox };
|
||||
@@ -0,0 +1,84 @@
|
||||
@import "../../Component/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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
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;
|
||||
keyI18nOption?: Record<string, string>;
|
||||
infoI18n?: AllI18nKeys;
|
||||
disableI18n?: AllI18nKeys;
|
||||
disableI18nOption?: Record<string, string>;
|
||||
errorI18n?: AllI18nKeys;
|
||||
errorI18nOption?: Record<string, string>;
|
||||
targetRef?: RefObject<HTMLDivElement>;
|
||||
customStyle?: boolean;
|
||||
customHoverStyle?: boolean;
|
||||
onClick?: () => any;
|
||||
}
|
||||
|
||||
class TextField extends Component<ITextFieldProps> {
|
||||
|
||||
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 <div
|
||||
className={classList.join(" ")}
|
||||
ref={this.props.targetRef}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
{ this.props.children }
|
||||
</div>
|
||||
}
|
||||
|
||||
private renderDisable() {
|
||||
return <div
|
||||
className={
|
||||
`${
|
||||
"text-field-content"
|
||||
} ${
|
||||
"text-field-content-styled"
|
||||
} ${
|
||||
"text-field-content-hover-styled"
|
||||
} ${
|
||||
"text-field-content-disable"
|
||||
}`
|
||||
}
|
||||
ref={this.props.targetRef}
|
||||
style={{ cursor: "not-allowed" }}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<Localization
|
||||
i18nKey={this.props.disableI18n ?? "Common.No.Unknown.Error"}
|
||||
options={this.props.disableI18nOption}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
private renderError() {
|
||||
return <div className="text-field-error-message">
|
||||
<Localization
|
||||
i18nKey={this.props.errorI18n ?? "Common.No.Unknown.Error"}
|
||||
options={this.props.errorI18nOption}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
return <>
|
||||
<Theme className="text-field-root" fontLevel={FontLevel.normal}>
|
||||
<div className="text-field-intro">
|
||||
<Localization i18nKey={this.props.keyI18n} options={this.props.keyI18nOption}/>
|
||||
</div>
|
||||
<div className="text-field-container">
|
||||
{
|
||||
this.props.disableI18n ?
|
||||
this.renderDisable() :
|
||||
this.renderInput()
|
||||
}
|
||||
{
|
||||
this.props.errorI18n ?
|
||||
this.renderError() :
|
||||
undefined
|
||||
}
|
||||
</div>
|
||||
</Theme>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
export { TextField, ITextFieldProps };
|
||||
@@ -0,0 +1,46 @@
|
||||
@import "../../Component/Theme/Theme.scss";
|
||||
|
||||
$line-min-height: 26px;
|
||||
|
||||
div.toggles-input {
|
||||
|
||||
div.checkbox {
|
||||
width: $line-min-height;
|
||||
height: $line-min-height;
|
||||
overflow: hidden;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
div.checkbox.red:hover {
|
||||
color: $lt-red !important;
|
||||
}
|
||||
}
|
||||
|
||||
div.dark.text-field-root {
|
||||
|
||||
div.toggles-input div.checkbox {
|
||||
background-color: $lt-bg-color-lvl3-dark;
|
||||
color: $lt-font-color-normal-dark;
|
||||
}
|
||||
|
||||
div.toggles-input div.checkbox:hover {
|
||||
background-color: $lt-bg-color-lvl2-dark;
|
||||
}
|
||||
}
|
||||
|
||||
div.light.text-field-root {
|
||||
|
||||
div.toggles-input div.checkbox {
|
||||
background-color: $lt-bg-color-lvl3-light;
|
||||
color: $lt-font-color-normal-light;
|
||||
}
|
||||
|
||||
div.toggles-input div.checkbox:hover {
|
||||
background-color: $lt-bg-color-lvl2-light;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Component, ReactNode } from "react";
|
||||
import { Icon } from "@fluentui/react";
|
||||
import { TextField, ITextFieldProps } from "@Input/TextField/TextField";
|
||||
import "./TogglesInput.scss";
|
||||
|
||||
interface ITogglesInputProps extends ITextFieldProps {
|
||||
value?: boolean;
|
||||
onIconName?: string;
|
||||
offIconName?: string;
|
||||
valueChange?: (value: boolean) => any;
|
||||
red?: boolean;
|
||||
}
|
||||
|
||||
class TogglesInput extends Component<ITogglesInputProps> {
|
||||
public render(): ReactNode {
|
||||
return <TextField
|
||||
{...this.props}
|
||||
className="toggles-input"
|
||||
keyI18n={this.props.keyI18n}
|
||||
customHoverStyle
|
||||
customStyle
|
||||
>
|
||||
<div
|
||||
className={"checkbox" + (this.props.red ? " red" : "")}
|
||||
style={{
|
||||
cursor: this.props.disableI18n ? "not-allowed" : "pointer"
|
||||
}}
|
||||
onClick={(() => {
|
||||
if (this.props.disableI18n) {
|
||||
return;
|
||||
}
|
||||
if (this.props.valueChange) {
|
||||
this.props.valueChange(!this.props.value);
|
||||
}
|
||||
})}
|
||||
>
|
||||
<Icon
|
||||
iconName={
|
||||
this.props.value ?
|
||||
this.props.onIconName ?? "CheckMark" :
|
||||
this.props.offIconName ?? undefined
|
||||
}
|
||||
style={{
|
||||
display: this.props.value ? "inline-block" :
|
||||
this.props.offIconName ? "inline-block" : "none"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TextField>;
|
||||
}
|
||||
}
|
||||
|
||||
export { TogglesInput };
|
||||
Reference in New Issue
Block a user