* initial layout * updated parser to support nested array * array field rendering * changes * ts fix * minor revert FormWidget * modified schema structure * select and switch fields * added checkbox field * added RadioGroupField * partial DateField and defaults, typing refactoring * added label and field type change * minor ts changes * changes * modified widget/utils for nested panelConfig, modified schema to object approach * array/object label support * hide field configuration when children not present * added tooltip * field visibility option * disabled state * upgraded tslib, form initial values * custom field configuration - add/hide/edit * field configuration - label change * return input when field configuration reaches max depth * minor changes * form - scroll, fixedfooter, enitity defn and other minior changes * form title * unregister on unmount * fixes * zero state * fix field padding * patched updating form values, removed linting warnings * configured action buttons * minor fix * minor change * property pane - sort fields in field configuration * refactor include all properties * checkbox properties * date properties * refactor typings and radio group properties * switch, multselect, select, array, object properties * minor changes * default value * ts fixes * checkbox field properties implementation * date field prop implementation * switch field * select field and fix deep nested meta properties * multiselect implementation * minor change * input field implementation * fix position jump on field type change * initial accordian * field state property and auto-complete of JSONFormComputeControl * merge fixes * renamed FormBuilder to JSONForm * source data validation minor change * custom field default value fix * Editable keys for custom field * minor fixes * replaced useFieldArray with custom logic, added widget icon * array and object accordian with border/background styling * minor change * disabled states for array and objects * default value minor fix * form level styles * modified logic for isDisabled for array and object, added disabledWhenInvalid, exposed isValid to fieldState for text input, removed useDisableChildren * added isValid for all field types * fixed reset to default values * debounce form values update * minor change * minor change * fix crash - source data change multi-select to array, fix crash - change of options * fix positioning * detect date type in source data * fix crash - when object is passed to regex input field * fixed default sourceData path for fields * accodion keep children mounted on collapse * jest test for schemaParser * widget/helper and useRegisterFieldInvalid test * tests for property config helper and generatePanelPropertyConfig * fix input field validation not appearing * fix date field type detection * rename data -> formData * handle null/undefined field value change in sourceData * added null/undefined as valid values for defaultValue text field * auto detect email field * set formData default value on initial load * switch field inline positioning * field margin fix for row direction * select full width * fiex date field default value - out of range * fix any field type to array * array default value logic change * base cypress test changes * initial json form render cy test * key sanitization * fix fieldState update logic * required design, object/array background color, accordion changes, fix - add new custom field * minor change * cypress tests * fix date formatted value, field state cypress test * cypress - field properties test and fixes * rename test file * fix accessort change to blank value, cypress tests * fix array field default value for modified accessor * minor fix * added animate loading * fix empty state, add new custom field * test data fix * fix warnings * fix timePrecision visibility * button styling * ported input v2 * fix jest tests * fix cypress tests * perf changes * perf improvement * added comments * multiselect changes * input field perf refactor * array field, object field refactor performance * checkbox field refactor * refectored date, radio, select and switch * fixes * test fixes * fixes * minor fix * rename field renderer * remove tracked fieldRenderer field * cypress test fixes * cypress changes * array default value fixes * arrayfield passedDefaultValue * auto enabled JS mode for few properties, reverted swith and date property controls * cypress changes * added widget sniping mode and fixed object passedDefaultValue * multiselect v2 * select v2 * fix jest tests * test fixes * field limit * rename field type dropdown texts * field type changes fixes * jest fixes * loading state submit button * default source data for new widget * modify limit message * multiseelct default value changes and cypress fix * select default value * keep default value intact on field type change * TextTable cypress text fix * review changes * fixed footer changes * collapse styles section by default * fixed footer changes * form modes * custom field key rentention * fixed footer fix in view mode * non ascii characters * fix meta merge in dataTreeWidget * minor fixes * rename useRegisterFieldInvalid.ts -> useRegisterFieldValidity.ts * modified dependency injection into evaluated values * refactored fixedfooter logic * minor change * accessor update * minor change * fixes * QA fixes date field, scroll content * fix phone number field, removed visiblity option from array item * fix sourceData autocomplete * reset logic * fix multiselect reset * form values hydration on widget drag * code review changes * reverted order of merge dataTreeWidget * fixes * added button titles, fixed hydration issue * default value fixes * upgraded react hook form, modified array-level/field-level default value logic * fixed select validation * added icon entity explorer, modified icon align control * modify accessor validation for mongo db _id * update email field regex * review changes * explicitly handle empty source data validation
741 lines
21 KiB
TypeScript
741 lines
21 KiB
TypeScript
import React, { MutableRefObject } from "react";
|
|
import styled from "styled-components";
|
|
import { labelStyle } from "constants/DefaultTheme";
|
|
import { ComponentProps } from "widgets/BaseComponent";
|
|
import {
|
|
FontStyleTypes,
|
|
TextSize,
|
|
TEXT_SIZES,
|
|
} from "constants/WidgetConstants";
|
|
import {
|
|
Alignment,
|
|
Intent,
|
|
NumericInput,
|
|
IconName,
|
|
InputGroup,
|
|
Label,
|
|
Classes,
|
|
ControlGroup,
|
|
TextArea,
|
|
Tag,
|
|
Position,
|
|
IRef,
|
|
} from "@blueprintjs/core";
|
|
import Tooltip from "components/ads/Tooltip";
|
|
import { ReactComponent as HelpIcon } from "assets/icons/control/help.svg";
|
|
import { IconWrapper } from "constants/IconConstants";
|
|
|
|
import { Colors } from "constants/Colors";
|
|
import _ from "lodash";
|
|
import {
|
|
createMessage,
|
|
INPUT_WIDGET_DEFAULT_VALIDATION_ERROR,
|
|
} from "@appsmith/constants/messages";
|
|
import { InputType, InputTypes } from "../constants";
|
|
|
|
import CurrencyTypeDropdown, {
|
|
CurrencyDropdownOptions,
|
|
getSelectedCurrency,
|
|
} from "./CurrencyCodeDropdown";
|
|
import ISDCodeDropdown, {
|
|
ISDCodeDropdownOptions,
|
|
getSelectedISDCode,
|
|
} from "./ISDCodeDropdown";
|
|
|
|
// TODO(abhinav): All of the following imports should not be in widgets.
|
|
import ErrorTooltip from "components/editorComponents/ErrorTooltip";
|
|
import Icon from "components/ads/Icon";
|
|
import { limitDecimalValue, getSeparators } from "./utilities";
|
|
|
|
/**
|
|
* All design system component specific logic goes here.
|
|
* Ex. Blueprint has a separate numeric input and text input so switching between them goes here
|
|
* Ex. To set the icon as currency, blue print takes in a set of defined types
|
|
* All generic logic like max characters for phone numbers should be 10, should go in the widget
|
|
*/
|
|
|
|
const InputComponentWrapper = styled((props) => (
|
|
<ControlGroup
|
|
{..._.omit(props, [
|
|
"hasError",
|
|
"numeric",
|
|
"labelTextColor",
|
|
"allowCurrencyChange",
|
|
"compactMode",
|
|
"labelStyle",
|
|
"labelTextSize",
|
|
"multiline",
|
|
"numeric",
|
|
"inputType",
|
|
])}
|
|
/>
|
|
))<{
|
|
numeric: boolean;
|
|
multiline: string;
|
|
hasError: boolean;
|
|
allowCurrencyChange?: boolean;
|
|
disabled?: boolean;
|
|
inputType: InputType;
|
|
}>`
|
|
flex-direction: ${(props) => (props.compactMode ? "row" : "column")};
|
|
&&&& {
|
|
.currency-type-filter,
|
|
.country-type-filter {
|
|
width: fit-content;
|
|
height: 36px;
|
|
position: absolute;
|
|
display: inline-block;
|
|
left: 0;
|
|
z-index: 16;
|
|
svg {
|
|
path {
|
|
fill: ${(props) => props.theme.colors.icon?.hover};
|
|
}
|
|
}
|
|
&:hover {
|
|
border: 1px solid ${Colors.GREY_5} !important;
|
|
}
|
|
}
|
|
.${Classes.INPUT} {
|
|
min-height: 36px;
|
|
${(props) =>
|
|
props.inputType === InputTypes.CURRENCY &&
|
|
props.allowCurrencyChange &&
|
|
`
|
|
padding-left: 45px;`};
|
|
${(props) =>
|
|
props.inputType === InputTypes.CURRENCY &&
|
|
!props.allowCurrencyChange &&
|
|
`
|
|
padding-left: 35px;`};
|
|
${(props) =>
|
|
props.inputType === InputTypes.PHONE_NUMBER &&
|
|
`padding-left: 85px;
|
|
`};
|
|
box-shadow: none;
|
|
border: 1px solid;
|
|
border-color: ${({ hasError }) =>
|
|
hasError ? `${Colors.DANGER_SOLID} !important;` : `${Colors.GREY_3};`}
|
|
border-radius: 0;
|
|
height: ${(props) => (props.multiline === "true" ? "100%" : "inherit")};
|
|
width: 100%;
|
|
${(props) =>
|
|
props.numeric &&
|
|
`
|
|
border-top-right-radius: 0px;
|
|
border-bottom-right-radius: 0px;
|
|
${props.hasError ? "" : "border-right-width: 0px;"}
|
|
`}
|
|
${(props) =>
|
|
props.inputType === "PASSWORD" &&
|
|
`
|
|
& + .bp3-input-action {
|
|
height: 36px;
|
|
width: 36px;
|
|
cursor: pointer;
|
|
padding: 1px;
|
|
.password-input {
|
|
color: ${Colors.GREY_6};
|
|
justify-content: center;
|
|
height: 100%;
|
|
svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
&:hover {
|
|
background-color: ${Colors.GREY_2};
|
|
color: ${Colors.GREY_10};
|
|
}
|
|
}
|
|
}
|
|
`}
|
|
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
|
&:active {
|
|
border-color: ${({ hasError }) =>
|
|
hasError ? Colors.DANGER_SOLID : Colors.HIT_GRAY};
|
|
}
|
|
&:hover {
|
|
border-left: 1px solid ${Colors.GREY_5};
|
|
border-right: 1px solid ${Colors.GREY_5};
|
|
border-color: ${Colors.GREY_5};
|
|
}
|
|
&:focus {
|
|
border-color: ${({ hasError }) =>
|
|
hasError ? Colors.DANGER_SOLID : Colors.MYSTIC};
|
|
|
|
&:focus {
|
|
outline: 0;
|
|
border: 1px solid ${Colors.GREEN_1};
|
|
box-shadow: 0px 0px 0px 2px ${Colors.GREEN_2} !important;
|
|
}
|
|
}
|
|
&:disabled {
|
|
background-color: ${Colors.GREY_1};
|
|
border: 1.2px solid ${Colors.GREY_3};
|
|
& + .bp3-input-action {
|
|
pointer-events: none;
|
|
}
|
|
}
|
|
}
|
|
.${Classes.INPUT}:disabled {
|
|
background: ${Colors.GREY_1};
|
|
color: ${Colors.GREY_7};
|
|
}
|
|
.${Classes.INPUT_GROUP} {
|
|
display: block;
|
|
margin: 0;
|
|
.bp3-tag {
|
|
background-color: transparent;
|
|
color: #5c7080;
|
|
}
|
|
&.${Classes.DISABLED} + .bp3-button-group.bp3-vertical {
|
|
button {
|
|
background: ${Colors.GREY_1};
|
|
}
|
|
}
|
|
}
|
|
.${Classes.CONTROL_GROUP} {
|
|
justify-content: flex-start;
|
|
}
|
|
height: 100%;
|
|
align-items: center;
|
|
label {
|
|
${labelStyle}
|
|
margin-right: 5px;
|
|
text-align: right;
|
|
align-self: flex-start;
|
|
color: ${(props) =>
|
|
props.disabled ? Colors.GREY_8 : props.labelTextColor || "inherit"};
|
|
font-size: ${(props) => props.labelTextSize};
|
|
font-weight: ${(props) =>
|
|
props?.labelStyle?.includes(FontStyleTypes.BOLD) ? "bold" : "normal"};
|
|
font-style: ${(props) =>
|
|
props?.labelStyle?.includes(FontStyleTypes.ITALIC) ? "italic" : ""};
|
|
text-decoration: ${(props) =>
|
|
props?.labelStyle?.includes(FontStyleTypes.UNDERLINE)
|
|
? "underline"
|
|
: ""};
|
|
}
|
|
}
|
|
`;
|
|
|
|
const StyledNumericInput = styled(NumericInput)`
|
|
&&&& .bp3-input-group {
|
|
display: flex;
|
|
> {
|
|
&:first-child:not(input) {
|
|
position: static;
|
|
background: ${(props) =>
|
|
props.disabled ? Colors.GREY_1 : Colors.WHITE};
|
|
border: 1.2px solid ${Colors.GREY_3};
|
|
color: ${(props) => (props.disabled ? Colors.GREY_7 : Colors.GREY_10)};
|
|
border-right: 0;
|
|
}
|
|
input:not(:first-child) {
|
|
padding-left: 5px;
|
|
border-left: 1px solid transparent;
|
|
z-index: 16;
|
|
line-height: 16px;
|
|
|
|
&:hover:not(:focus):not(:disabled) {
|
|
border-left: 1px solid ${Colors.GREY_5};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
&&&& .bp3-button-group.bp3-vertical {
|
|
border: 1.2px solid ${Colors.GREY_3};
|
|
border-left: none;
|
|
button {
|
|
background: ${Colors.WHITE};
|
|
box-shadow: none;
|
|
min-width: 24px;
|
|
width: 24px;
|
|
border-radius: 0;
|
|
&:hover {
|
|
background: ${Colors.GREY_2};
|
|
span {
|
|
color: ${Colors.GREY_10};
|
|
}
|
|
}
|
|
&:focus {
|
|
border: 1px solid ${Colors.GREEN_1};
|
|
box-shadow: 0px 0px 0px 2px ${Colors.GREEN_2};
|
|
}
|
|
span {
|
|
color: ${Colors.GREY_6};
|
|
svg {
|
|
width: 14px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
|
|
const ToolTipIcon = styled(IconWrapper)`
|
|
cursor: help;
|
|
margin-top: 1.5px;
|
|
&&&:hover {
|
|
svg {
|
|
path {
|
|
fill: #716e6e;
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
|
|
const TextLableWrapper = styled.div<{
|
|
compactMode: boolean;
|
|
}>`
|
|
${(props) =>
|
|
props.compactMode ? "&&& {margin-right: 5px;}" : "width: 100%;"}
|
|
display: flex;
|
|
max-height: 20px;
|
|
`;
|
|
|
|
const TextInputWrapper = styled.div`
|
|
width: 100%;
|
|
display: flex;
|
|
flex: 1;
|
|
`;
|
|
|
|
export const isNumberInputType = (inputType: InputType) => {
|
|
return (
|
|
inputType === "INTEGER" ||
|
|
inputType === "NUMBER" ||
|
|
inputType === "CURRENCY" ||
|
|
inputType === "PHONE_NUMBER"
|
|
);
|
|
};
|
|
class InputComponent extends React.Component<
|
|
InputComponentProps,
|
|
InputComponentState
|
|
> {
|
|
groupSeparator: string;
|
|
decimalSeparator: string;
|
|
constructor(props: InputComponentProps) {
|
|
super(props);
|
|
this.state = { showPassword: false };
|
|
const separators = getSeparators();
|
|
this.groupSeparator = separators.groupSeparator;
|
|
this.decimalSeparator = separators.decimalSeparator;
|
|
}
|
|
|
|
componentDidMount() {
|
|
if (this.props.inputType === InputTypes.CURRENCY) {
|
|
const element: any = document.querySelectorAll(
|
|
`.appsmith_widget_${this.props.widgetId} .bp3-button`,
|
|
);
|
|
if (element !== null) {
|
|
element[0].addEventListener("click", this.onIncrementButtonClick);
|
|
element[1].addEventListener("click", this.onDecrementButtonClick);
|
|
}
|
|
}
|
|
}
|
|
|
|
componentDidUpdate(prevProps: InputComponentProps) {
|
|
if (
|
|
this.props.inputType === InputTypes.CURRENCY &&
|
|
this.props.inputType !== prevProps.inputType
|
|
) {
|
|
const element: any = document.querySelectorAll(
|
|
`.appsmith_widget_${this.props.widgetId} .bp3-button`,
|
|
);
|
|
if (element !== null) {
|
|
element[0].addEventListener("click", this.onIncrementButtonClick);
|
|
element[1].addEventListener("click", this.onDecrementButtonClick);
|
|
}
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
if (this.props.inputType === InputTypes.CURRENCY) {
|
|
const element: any = document.querySelectorAll(
|
|
`.appsmith_widget_${this.props.widgetId} .bp3-button`,
|
|
);
|
|
if (element !== null) {
|
|
element[0].removeEventListener("click", this.onIncrementButtonClick);
|
|
element[1].removeEventListener("click", this.onDecrementButtonClick);
|
|
}
|
|
}
|
|
}
|
|
|
|
updateValueOnButtonClick = (type: number) => {
|
|
const deFormattedValue: string | number = this.props.value
|
|
.split(this.groupSeparator)
|
|
.join("");
|
|
const stepSize = this.props.stepSize || 1;
|
|
this.props.onValueChange(
|
|
String(Number(deFormattedValue) + stepSize * type),
|
|
);
|
|
};
|
|
|
|
onIncrementButtonClick = (e: React.MouseEvent) => {
|
|
this.updateValueOnButtonClick(1);
|
|
e.preventDefault();
|
|
};
|
|
|
|
onDecrementButtonClick = (e: React.MouseEvent) => {
|
|
this.updateValueOnButtonClick(-1);
|
|
e.preventDefault();
|
|
};
|
|
|
|
setFocusState = (isFocused: boolean) => {
|
|
this.props.onFocusChange(isFocused);
|
|
};
|
|
|
|
onTextChange = (
|
|
event:
|
|
| React.ChangeEvent<HTMLInputElement>
|
|
| React.ChangeEvent<HTMLTextAreaElement>,
|
|
) => {
|
|
this.props.onValueChange(event.target.value);
|
|
};
|
|
|
|
onNumberChange = (
|
|
valueAsNum: number,
|
|
valueAsString: string,
|
|
inputElement: HTMLInputElement,
|
|
) => {
|
|
if (this.props.inputType === InputTypes.CURRENCY) {
|
|
//handle this only when input is focussed
|
|
if (inputElement.className.includes("focus-visible")) {
|
|
const fractionDigits = this.props.decimalsInCurrency || 0;
|
|
const currentIndexOfDecimal = valueAsString.indexOf(
|
|
this.decimalSeparator,
|
|
);
|
|
const indexOfDecimal = valueAsString.length - fractionDigits - 1;
|
|
if (
|
|
valueAsString.includes(this.decimalSeparator) &&
|
|
currentIndexOfDecimal <= indexOfDecimal
|
|
) {
|
|
const value = limitDecimalValue(
|
|
this.props.decimalsInCurrency,
|
|
valueAsString,
|
|
this.decimalSeparator,
|
|
this.groupSeparator,
|
|
);
|
|
this.props.onValueChange(value);
|
|
} else {
|
|
this.props.onValueChange(valueAsString);
|
|
}
|
|
}
|
|
} else {
|
|
this.props.onValueChange(valueAsString);
|
|
}
|
|
};
|
|
|
|
getLeftIcon = (inputType: InputType, disabled: boolean) => {
|
|
if (inputType === InputTypes.PHONE_NUMBER) {
|
|
const selectedISDCode = getSelectedISDCode(
|
|
this.props.phoneNumberCountryCode,
|
|
);
|
|
return (
|
|
<ISDCodeDropdown
|
|
disabled={disabled}
|
|
onISDCodeChange={this.props.onISDCodeChange}
|
|
options={ISDCodeDropdownOptions}
|
|
selected={selectedISDCode}
|
|
/>
|
|
);
|
|
} else if (inputType === InputTypes.CURRENCY) {
|
|
const selectedCurrencyCountryCode = getSelectedCurrency(
|
|
this.props.currencyCountryCode,
|
|
);
|
|
return (
|
|
<CurrencyTypeDropdown
|
|
allowCurrencyChange={this.props.allowCurrencyChange && !disabled}
|
|
onCurrencyTypeChange={this.props.onCurrencyTypeChange}
|
|
options={CurrencyDropdownOptions}
|
|
selected={selectedCurrencyCountryCode}
|
|
/>
|
|
);
|
|
} else if (this.props.iconName && this.props.iconAlign === "left") {
|
|
return this.props.iconName;
|
|
}
|
|
return this.props.leftIcon;
|
|
};
|
|
|
|
getIcon(inputType: InputType) {
|
|
switch (inputType) {
|
|
case "SEARCH":
|
|
return "search";
|
|
case "EMAIL":
|
|
return "envelope";
|
|
default:
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
getType(inputType: InputType) {
|
|
switch (inputType) {
|
|
case "PASSWORD":
|
|
return this.state.showPassword ? "text" : "password";
|
|
case "EMAIL":
|
|
return "email";
|
|
case "SEARCH":
|
|
return "search";
|
|
default:
|
|
return "text";
|
|
}
|
|
}
|
|
onKeyDownTextArea = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
const isEnterKey = e.key === "Enter" || e.keyCode === 13;
|
|
const { disableNewLineOnPressEnterKey } = this.props;
|
|
if (isEnterKey && disableNewLineOnPressEnterKey && !e.shiftKey) {
|
|
e.preventDefault();
|
|
}
|
|
if (typeof this.props.onKeyDown === "function") {
|
|
this.props.onKeyDown(e);
|
|
}
|
|
};
|
|
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
if (typeof this.props.onKeyDown === "function") {
|
|
this.props.onKeyDown(e);
|
|
}
|
|
};
|
|
|
|
onNumberInputBlur = () => {
|
|
this.setFocusState(false);
|
|
};
|
|
|
|
onNumberInputFocus = () => {
|
|
this.setFocusState(true);
|
|
};
|
|
|
|
private numericInputComponent = () => {
|
|
const leftIcon = this.getLeftIcon(
|
|
this.props.inputType,
|
|
!!this.props.disabled,
|
|
);
|
|
const minorStepSize =
|
|
this.props.inputType === InputTypes.CURRENCY
|
|
? this.props.decimalsInCurrency || 0
|
|
: 0;
|
|
return (
|
|
<StyledNumericInput
|
|
allowNumericCharactersOnly
|
|
autoFocus={this.props.autoFocus}
|
|
className={this.props.isLoading ? "bp3-skeleton" : Classes.FILL}
|
|
disabled={this.props.disabled}
|
|
intent={this.props.intent}
|
|
leftIcon={leftIcon}
|
|
max={this.props.maxNum}
|
|
maxLength={this.props.maxChars}
|
|
min={
|
|
this.props.inputType === InputTypes.PHONE_NUMBER
|
|
? 0
|
|
: this.props.minNum
|
|
}
|
|
minorStepSize={
|
|
minorStepSize === 0 ? undefined : Math.pow(10, -1 * minorStepSize)
|
|
}
|
|
onBlur={this.onNumberInputBlur}
|
|
onFocus={this.onNumberInputFocus}
|
|
onKeyDown={this.onKeyDown}
|
|
onValueChange={this.onNumberChange}
|
|
placeholder={this.props.placeholder}
|
|
stepSize={minorStepSize === 0 ? this.props.stepSize : undefined}
|
|
value={this.props.value}
|
|
/>
|
|
);
|
|
};
|
|
private textAreaInputComponent = () => (
|
|
<TextArea
|
|
autoFocus={this.props.autoFocus}
|
|
className={this.props.isLoading ? "bp3-skeleton" : ""}
|
|
disabled={this.props.disabled}
|
|
growVertically={false}
|
|
inputRef={this.props.inputRef as IRef<HTMLTextAreaElement>}
|
|
intent={this.props.intent}
|
|
maxLength={this.props.maxChars}
|
|
onBlur={() => this.setFocusState(false)}
|
|
onChange={this.onTextChange}
|
|
onFocus={() => this.setFocusState(true)}
|
|
onKeyDown={this.onKeyDownTextArea}
|
|
placeholder={this.props.placeholder}
|
|
style={{ resize: "none" }}
|
|
value={this.props.value}
|
|
/>
|
|
);
|
|
|
|
private textInputComponent = (isTextArea: boolean) =>
|
|
isTextArea ? (
|
|
this.textAreaInputComponent()
|
|
) : (
|
|
<InputGroup
|
|
autoFocus={this.props.autoFocus}
|
|
className={this.props.isLoading ? "bp3-skeleton" : ""}
|
|
disabled={this.props.disabled}
|
|
inputRef={this.props.inputRef as IRef<HTMLInputElement>}
|
|
intent={this.props.intent}
|
|
leftIcon={
|
|
this.props.iconName && this.props.iconAlign === "left"
|
|
? this.props.iconName
|
|
: undefined
|
|
}
|
|
maxLength={this.props.maxChars}
|
|
onBlur={() => this.setFocusState(false)}
|
|
onChange={this.onTextChange}
|
|
onFocus={() => this.setFocusState(true)}
|
|
onKeyDown={this.onKeyDown}
|
|
placeholder={this.props.placeholder}
|
|
rightElement={
|
|
this.props.inputType === "PASSWORD" ? (
|
|
<Icon
|
|
className="password-input"
|
|
name={this.state.showPassword ? "eye-off" : "eye-on"}
|
|
onClick={() => {
|
|
this.setState({ showPassword: !this.state.showPassword });
|
|
}}
|
|
/>
|
|
) : this.props.iconName && this.props.iconAlign === "right" ? (
|
|
<Tag icon={this.props.iconName} />
|
|
) : (
|
|
undefined
|
|
)
|
|
}
|
|
spellCheck={this.props.spellCheck}
|
|
type={this.getType(this.props.inputType)}
|
|
value={this.props.value}
|
|
/>
|
|
);
|
|
private renderInputComponent = (inputType: InputType, isTextArea: boolean) =>
|
|
isNumberInputType(inputType)
|
|
? this.numericInputComponent()
|
|
: this.textInputComponent(isTextArea);
|
|
|
|
render() {
|
|
const {
|
|
label,
|
|
labelStyle,
|
|
labelTextColor,
|
|
labelTextSize,
|
|
tooltip,
|
|
} = this.props;
|
|
const showLabelHeader = label || tooltip;
|
|
|
|
return (
|
|
<InputComponentWrapper
|
|
allowCurrencyChange={this.props.allowCurrencyChange}
|
|
compactMode={this.props.compactMode}
|
|
disabled={this.props.disabled}
|
|
fill
|
|
hasError={this.props.isInvalid}
|
|
inputType={this.props.inputType}
|
|
labelStyle={labelStyle}
|
|
labelTextColor={labelTextColor}
|
|
labelTextSize={labelTextSize ? TEXT_SIZES[labelTextSize] : "inherit"}
|
|
multiline={this.props.multiline.toString()}
|
|
numeric={isNumberInputType(this.props.inputType)}
|
|
>
|
|
{showLabelHeader && (
|
|
<TextLableWrapper
|
|
className="t--input-label-wrapper"
|
|
compactMode={this.props.compactMode}
|
|
>
|
|
{this.props.label && (
|
|
<Label
|
|
className={`
|
|
t--input-widget-label ${
|
|
this.props.isLoading
|
|
? Classes.SKELETON
|
|
: Classes.TEXT_OVERFLOW_ELLIPSIS
|
|
}
|
|
`}
|
|
>
|
|
{this.props.label}
|
|
</Label>
|
|
)}
|
|
{this.props.tooltip && (
|
|
<Tooltip
|
|
content={this.props.tooltip}
|
|
hoverOpenDelay={200}
|
|
position={Position.TOP}
|
|
>
|
|
<ToolTipIcon
|
|
color={Colors.SILVER_CHALICE}
|
|
height={14}
|
|
width={14}
|
|
>
|
|
<HelpIcon className="t--input-widget-tooltip" />
|
|
</ToolTipIcon>
|
|
</Tooltip>
|
|
)}
|
|
</TextLableWrapper>
|
|
)}
|
|
<TextInputWrapper>
|
|
<ErrorTooltip
|
|
isOpen={this.props.isInvalid && this.props.showError}
|
|
message={
|
|
this.props.errorMessage ||
|
|
createMessage(INPUT_WIDGET_DEFAULT_VALIDATION_ERROR)
|
|
}
|
|
>
|
|
{this.renderInputComponent(
|
|
this.props.inputType,
|
|
this.props.multiline,
|
|
)}
|
|
</ErrorTooltip>
|
|
</TextInputWrapper>
|
|
</InputComponentWrapper>
|
|
);
|
|
}
|
|
}
|
|
|
|
export interface InputComponentState {
|
|
showPassword?: boolean;
|
|
}
|
|
|
|
export interface InputComponentProps extends ComponentProps {
|
|
value: string;
|
|
inputType: InputType;
|
|
disabled?: boolean;
|
|
intent?: Intent;
|
|
defaultValue?: string | number;
|
|
currencyCountryCode?: string;
|
|
noOfDecimals?: number;
|
|
phoneNumberCountryCode?: string;
|
|
spellCheck: boolean;
|
|
allowCurrencyChange?: boolean;
|
|
decimalsInCurrency?: number;
|
|
label: string;
|
|
labelTextColor?: string;
|
|
labelTextSize?: TextSize;
|
|
labelStyle?: string;
|
|
tooltip?: string;
|
|
leftIcon?: IconName;
|
|
allowNumericCharactersOnly?: boolean;
|
|
fill?: boolean;
|
|
errorMessage?: string;
|
|
maxChars?: number;
|
|
maxNum?: number;
|
|
minNum?: number;
|
|
onValueChange: (valueAsString: string) => void;
|
|
onCurrencyTypeChange: (code?: string) => void;
|
|
onISDCodeChange: (code?: string) => void;
|
|
stepSize?: number;
|
|
placeholder?: string;
|
|
isLoading: boolean;
|
|
multiline: boolean;
|
|
compactMode: boolean;
|
|
isInvalid: boolean;
|
|
autoFocus?: boolean;
|
|
iconName?: IconName;
|
|
iconAlign?: Omit<Alignment, "center">;
|
|
showError: boolean;
|
|
onFocusChange: (state: boolean) => void;
|
|
disableNewLineOnPressEnterKey?: boolean;
|
|
inputRef?: MutableRefObject<
|
|
HTMLTextAreaElement | HTMLInputElement | null | undefined
|
|
>;
|
|
name?: string;
|
|
onKeyDown?: (
|
|
e:
|
|
| React.KeyboardEvent<HTMLTextAreaElement>
|
|
| React.KeyboardEvent<HTMLInputElement>,
|
|
) => void;
|
|
}
|
|
|
|
export default InputComponent;
|