mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Installing extensions UI improvements (#1522)
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
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,
|
||||
|
||||
@@ -89,8 +89,10 @@
|
||||
|
||||
&.theme {
|
||||
&.round-black {
|
||||
&.invalid label {
|
||||
border-color: $colorSoftError !important;
|
||||
&.invalid.dirty {
|
||||
label {
|
||||
border-color: $colorSoftError;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user