this.onInputBlur()}
+ >
+ {this.props.leftIcon ? this.props.leftIcon() : ""}
+ {this.props.prefix ?
{this.props.prefix} : undefined}
+ {this.props.inputBox ?
+
{this.props.inputBox()} :
+
this.props.onInput(event.currentTarget.value))}
+ onKeyDown={e => this.onKeyDown(e)}
+ />}
+ {this.props.rightIcon ? this.props.rightIcon() : ""}
+
+ )
+ }
+
+ focusInput() {
+ this.refInput.current?.focus();
+ }
+
+ private onKeyDown(event: React.KeyboardEvent) {
+ this.inputEdited = true;
+
+ if(event.key === "Enter")
+ this.refInput.current?.blur();
+ }
+
+ private onInputBlur() {
+ if(this.props.onChange && this.inputEdited) {
+ this.inputEdited = false;
+ this.props.onChange(this.refInput.current.value);
+ }
+
+ if(this.props.onBlur)
+ this.props.onBlur();
+ }
+}
\ No newline at end of file
diff --git a/shared/js/ui/react-elements/LoadingDots.tsx b/shared/js/ui/react-elements/LoadingDots.tsx
new file mode 100644
index 00000000..2cf81498
--- /dev/null
+++ b/shared/js/ui/react-elements/LoadingDots.tsx
@@ -0,0 +1,19 @@
+import {useEffect, useState} from "react";
+import * as React from "react";
+
+export const LoadingDots = (props: { maxDots?: number, speed?: number }) => {
+ if(!props.maxDots || props.maxDots < 1)
+ props.maxDots = 3;
+
+ const [dots, setDots] = useState(0);
+
+ useEffect(() => {
+ const timeout = setTimeout(() => setDots(dots + 1), props.speed || 500);
+ return () => clearTimeout(timeout);
+ });
+
+ let result = ".";
+ for(let index = 0; index < dots % props.maxDots; index++)
+ result += ".";
+ return