Installing extensions UI improvements (#1522)

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman authored and GitHub committed 2020-11-25 16:42:19 +02:00
1 parent 73724b5a54
commit 7243dfdce4
13 files changed
+186 -217

No files matched your search

@@ -61,7 +61,7 @@ export class DropFileInput<T extends HTMLElement = any> extends React.Component<
const isValidContentElem = React.isValidElement(contentElem);
if (isValidContentElem) {
const contentElemProps: React.HTMLProps<HTMLElement> = {
className: cssNames("DropFileInput", className, {
className: cssNames("DropFileInput", contentElem.props.className, className, {
droppable: this.dropAreaActive,
}),
onDragEnter,
+4 -2
View File
@@ -89,8 +89,10 @@
&.theme {
&.round-black {
&.invalid label {
border-color: $colorSoftError !important;
&.invalid.dirty {
label {
border-color: $colorSoftError;
}
}
label {
+24 -13
View File
@@ -3,13 +3,13 @@ import "./input.scss";
import React, { DOMAttributes, InputHTMLAttributes, TextareaHTMLAttributes } from "react";
import { autobind, cssNames, debouncePromise, getRandId } from "../../utils";
import { Icon } from "../icon";
import { Tooltip, TooltipProps } from "../tooltip";
import * as Validators from "./input_validators";
import { InputValidator } from "./input_validators";
import isString from "lodash/isString";
import isFunction from "lodash/isFunction";
import isBoolean from "lodash/isBoolean";
import uniqueId from "lodash/uniqueId";
import { Tooltip } from "../tooltip";
const { conditionalValidators, ...InputValidators } = Validators;
export { InputValidators, InputValidator };
@@ -26,7 +26,7 @@ export type InputProps<T = string> = Omit<InputElementProps, "onChange" | "onSub
maxRows?: number; // when multiLine={true} define max rows size
dirty?: boolean; // show validation errors even if the field wasn't touched yet
showValidationLine?: boolean; // show animated validation line for async validators
showErrorsAsTooltip?: boolean; // show validation errors as a tooltip :hover (instead of block below)
showErrorsAsTooltip?: boolean | Omit<TooltipProps, "targetId">; // show validation errors as a tooltip :hover (instead of block below)
iconLeft?: string | React.ReactNode; // material-icon name in case of string-type
iconRight?: string | React.ReactNode;
contentRight?: string | React.ReactNode; // Any component of string goes after iconRight
@@ -63,6 +63,10 @@ export class Input extends React.Component<InputProps, State> {
errors: [],
};
isValid() {
return this.state.valid;
}
setValue(value: string) {
if (value !== this.getValue()) {
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(this.input.constructor.prototype, "value").set;
@@ -268,7 +272,8 @@ export class Input extends React.Component<InputProps, State> {
render() {
const {
multiLine, showValidationLine, validators, theme, maxRows, children, showErrorsAsTooltip,
maxLength, rows, disabled, autoSelectOnFocus, iconLeft, iconRight, contentRight,
maxLength, rows, disabled, autoSelectOnFocus, iconLeft, iconRight, contentRight, id,
dirty: _dirty, // excluded from passing to input-element
...inputProps
} = this.props;
const { focused, dirty, valid, validating, errors } = this.state;
@@ -294,29 +299,35 @@ export class Input extends React.Component<InputProps, State> {
ref: this.bindRef,
spellCheck: "false",
});
const tooltipId = showErrorsAsTooltip ? getRandId({ prefix: "input_tooltip_id" }) : undefined;
const showErrors = errors.length > 0 && !valid && dirty;
const errorsInfo = (
<div className="errors box grow">
{errors.map((error, i) => <p key={i}>{error}</p>)}
</div>
);
const componentId = id || showErrorsAsTooltip ? getRandId({ prefix: "input_tooltip_id" }) : undefined;
let tooltipError: React.ReactNode;
if (showErrorsAsTooltip && showErrors) {
const tooltipProps = typeof showErrorsAsTooltip === "object" ? showErrorsAsTooltip : {};
tooltipProps.className = cssNames("InputTooltipError", tooltipProps.className);
tooltipError = (
<Tooltip targetId={componentId} {...tooltipProps}>
<div className="flex gaps align-center">
<Icon material="error_outline"/>
{errorsInfo}
</div>
</Tooltip>
);
}
return (
<div id={tooltipId} className={className}>
<div id={componentId} className={className}>
{tooltipError}
<label className="input-area flex gaps align-center" id="">
{isString(iconLeft) ? <Icon material={iconLeft}/> : iconLeft}
{multiLine ? <textarea {...inputProps as any} /> : <input {...inputProps as any} />}
{isString(iconRight) ? <Icon material={iconRight}/> : iconRight}
{contentRight}
</label>
{showErrorsAsTooltip && showErrors && (
<Tooltip targetId={tooltipId} className="InputTooltipError">
<div className="flex gaps align-center">
<Icon material="error_outline"/>
{errorsInfo}
</div>
</Tooltip>
)}
<div className="input-info flex gaps">
{!showErrorsAsTooltip && showErrors && errorsInfo}
{this.showMaxLenIndicator && (
@@ -39,13 +39,13 @@ export const isNumber: InputValidator = {
export const isUrl: InputValidator = {
condition: ({ type }) => type === "url",
message: () => _i18n._(t`Wrong url format`),
validate: value => !!value.match(/^$|^http(s)?:\/\/\w+(\.\w+)*(:[0-9]+)?\/?(\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)*$/),
validate: value => !!value.match(/^http(s)?:\/\/\w+(\.\w+)*(:[0-9]+)?\/?(\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)*$/),
};
export const isPath: InputValidator = {
condition: ({ type }) => type === "text",
message: () => _i18n._(t`This field must be a valid path`),
validate: value => !value || fse.pathExistsSync(value),
validate: value => value && fse.pathExistsSync(value),
};
export const minLength: InputValidator = {
+31 -27
View File
@@ -10,13 +10,15 @@ import { Input, InputProps } from "./input";
interface Props extends InputProps {
compact?: boolean; // show only search-icon when not focused
closeIcon?: boolean;
onClear?: () => void;
bindGlobalFocusHotkey?: boolean;
showClearIcon?: boolean;
onClear?(): void;
}
const defaultProps: Partial<Props> = {
autoFocus: true,
closeIcon: true,
bindGlobalFocusHotkey: true,
showClearIcon: true,
get placeholder() {
return _i18n._(t`Search...`);
},
@@ -26,27 +28,27 @@ const defaultProps: Partial<Props> = {
export class SearchInput extends React.Component<Props> {
static defaultProps = defaultProps as object;
private input = createRef<Input>();
private inputRef = createRef<Input>();
componentDidMount() {
addEventListener("keydown", this.focus);
if (!this.props.bindGlobalFocusHotkey) return;
window.addEventListener("keydown", this.onGlobalKey);
}
componentWillUnmount() {
removeEventListener("keydown", this.focus);
window.removeEventListener("keydown", this.onGlobalKey);
}
clear = () => {
if (this.props.onClear) {
this.props.onClear();
@autobind()
onGlobalKey(evt: KeyboardEvent) {
const meta = evt.metaKey || evt.ctrlKey;
if (meta && evt.key === "f") {
this.inputRef.current.focus();
}
};
}
onChange = (val: string, evt: React.ChangeEvent<any>) => {
this.props.onChange(val, evt);
};
onKeyDown = (evt: React.KeyboardEvent<any>) => {
@autobind()
onKeyDown(evt: React.KeyboardEvent<any>) {
if (this.props.onKeyDown) {
this.props.onKeyDown(evt);
}
@@ -56,29 +58,31 @@ export class SearchInput extends React.Component<Props> {
this.clear();
evt.stopPropagation();
}
};
}
@autobind()
focus(evt: KeyboardEvent) {
const meta = evt.metaKey || evt.ctrlKey;
if (meta && evt.key == "f") {
this.input.current.focus();
clear() {
if (this.props.onClear) {
this.props.onClear();
} else {
this.inputRef.current.setValue("");
}
}
render() {
const { className, compact, closeIcon, onClear, ...inputProps } = this.props;
const icon = this.props.value
? closeIcon ? <Icon small material="close" onClick={this.clear}/> : null
: <Icon small material="search"/>;
const { className, compact, onClear, showClearIcon, bindGlobalFocusHotkey, value, ...inputProps } = this.props;
let rightIcon = <Icon small material="search"/>;
if (showClearIcon && value) {
rightIcon = <Icon small material="close" onClick={this.clear}/>;
}
return (
<Input
{...inputProps}
className={cssNames("SearchInput", className, { compact })}
onChange={this.onChange}
value={value}
onKeyDown={this.onKeyDown}
iconRight={icon}
ref={this.input}
iconRight={rightIcon}
ref={this.inputRef}
/>
);
}