🤖 Switch InputField + InputFieldWithIcon JSX components to TS, add more test coverage, fix Storybook build (#43307)

Zed + Opus 4.6; prompt: Convert the InputField JSX component to
TypeScript and remove the ts-ignore directives that we no longer need
after doing so.

- [x] Changes file added
- [x] Automated tests updated
This commit is contained in:
Ian Littman 2026-04-09 08:41:48 -05:00 committed by GitHub
parent f829170923
commit 2891904f31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
74 changed files with 842 additions and 651 deletions

View file

@ -7,6 +7,19 @@ import type { StorybookConfig } from "@storybook/react-webpack5";
const config: StorybookConfig = {
webpackFinal: async (config) => {
if (!config.resolve) {
config.resolve = {};
}
if (!config.resolve.alias) {
config.resolve.alias = {};
}
(config.resolve.alias as Record<string, string>)[
"node-sql-parser"
] = path.resolve(
__dirname,
"../node_modules/@sgress454/node-sql-parser/umd/sqlite.umd.js"
);
config.module?.rules?.push({
test: /\.scss$/,
use: [

View file

@ -0,0 +1 @@
* Moved some core UI form components from TypeScript for better predictability/reliability

View file

@ -6,7 +6,6 @@ import { AppContext } from "context/app";
import CustomLink from "components/CustomLink";
import Radio from "components/forms/fields/Radio";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
type EnrollmentType = "workProfile" | "fullyManaged";

View file

@ -4,7 +4,6 @@ import CustomLink from "components/CustomLink";
import PATHS from "router/paths";
import { AppContext } from "context/app";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
const generateUrl = (serverUrl: string, enrollSecret: string) => {

View file

@ -9,7 +9,6 @@ import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants";
import Button from "components/buttons/Button";
import Icon from "components/Icon/Icon";
import RevealButton from "components/buttons/RevealButton";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import TooltipWrapper from "components/TooltipWrapper";
import TabNav from "components/TabNav";

View file

@ -5,7 +5,6 @@ import { IEnrollSecret } from "interfaces/enroll_secret";
import Modal from "components/Modal";
import Button from "components/buttons/Button";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
interface ISecretEditorModalProps {

View file

@ -89,7 +89,6 @@ const TargetsInput = ({
type="search"
iconSvg="search"
value={searchText}
iconPosition="start"
label={label}
placeholder={placeholder}
onChange={setSearchText}

View file

@ -3,7 +3,6 @@ import React, { useCallback, useState } from "react";
import validateEquality from "components/forms/validators/validate_equality";
import Button from "components/buttons/Button";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import { IInputFieldParseTarget } from "interfaces/form_field";
@ -111,7 +110,7 @@ const ConfirmInviteForm = ({
value={name}
error={formErrors.name}
parseTarget
maxLength={80}
inputOptions={{ maxLength: 80 }}
/>
<InputField
label="Password"

View file

@ -10,11 +10,11 @@ const baseClass = "form-field";
export interface IFormFieldProps {
children: JSX.Element;
label: Array<any> | JSX.Element | string;
label: React.ReactNode;
name: string;
helpText?: Array<any> | JSX.Element | string;
helpText?: React.ReactNode;
type?: string;
error?: string;
error?: string | null;
className?: string;
tooltip?: React.ReactNode;
labelTooltipPosition?: PlacesType;

View file

@ -1,291 +0,0 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import classnames from "classnames";
import { noop, pick } from "lodash";
import { stringToClipboard } from "utilities/copy_text";
import FormField from "components/forms/FormField";
import Button from "components/buttons/Button";
import Icon from "components/Icon";
const baseClass = "input-field";
class InputField extends Component {
static propTypes = {
autofocus: PropTypes.bool,
/** readOnly displays a non-editable field */
readOnly: PropTypes.bool,
/** disabled displays a greyed out non-editable field */
disabled: PropTypes.bool,
error: PropTypes.string,
inputClassName: PropTypes.string, // eslint-disable-line react/forbid-prop-types
inputWrapperClass: PropTypes.string,
inputOptions: PropTypes.object, // eslint-disable-line react/forbid-prop-types
name: PropTypes.string,
onChange: PropTypes.func,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
placeholder: PropTypes.string,
type: PropTypes.string,
blockAutoComplete: PropTypes.bool,
value: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.string,
PropTypes.number,
]).isRequired,
/** Returns both name and value */
parseTarget: PropTypes.bool,
tooltip: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
labelTooltipPosition: PropTypes.string,
helpText: PropTypes.oneOfType([
PropTypes.string,
PropTypes.arrayOf(PropTypes.string),
PropTypes.object,
]),
/** Use in conjunction with type "password" and enableCopy to see eye icon to view */
enableShowSecret: PropTypes.bool,
enableCopy: PropTypes.bool,
ignore1password: PropTypes.bool,
// Accepts string or number for HTML compatibility, (e.g., step="0.1", step={0.1})
/** Only effective on input type number */
step: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** Only effective on input type number */
min: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** Only effective on input type number */
max: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};
static defaultProps = {
autofocus: false,
inputWrapperClass: "",
inputOptions: {},
label: null,
labelClassName: "",
onFocus: noop,
onBlur: noop,
type: "text",
blockAutoComplete: false,
value: "",
parseTarget: false,
tooltip: "",
labelTooltipPosition: undefined,
helpText: "",
enableCopy: false,
enableShowSecret: false,
ignore1password: false,
step: undefined,
min: undefined,
max: undefined,
};
constructor() {
super();
this.state = {
copied: false,
};
}
componentDidMount() {
const { autofocus } = this.props;
const { input } = this;
if (autofocus) {
input.focus();
}
return false;
}
onInputChange = (evt) => {
evt.preventDefault();
const { value, name } = evt.target;
const { onChange, parseTarget } = this.props;
if (parseTarget) {
// Returns both name and value
return onChange({ value, name });
}
return onChange(value);
};
onToggleSecret = (evt) => {
evt.preventDefault();
this.setState({ showSecret: !this.state.showSecret });
return false;
};
onClickCopy = (e) => {
e.preventDefault();
stringToClipboard(this.props.value).then(() => {
this.setState({ copied: true });
setTimeout(() => {
this.setState({ copied: false });
}, 2000);
});
};
renderShowSecretButton = () => {
const { onToggleSecret } = this;
return (
<Button
variant="icon"
className={`${baseClass}__show-secret-icon`}
onClick={onToggleSecret}
size="small"
>
<Icon name="eye" />
</Button>
);
};
renderCopyButton = () => {
const { onClickCopy } = this;
const copyButtonValue = <Icon name="copy" />;
const wrapperClasses = classnames(`${baseClass}__copy-wrapper`, {
[`${baseClass}__copy-wrapper__text-area`]: this.props.type === "textarea",
});
const copiedConfirmationClasses = classnames(
`${baseClass}__copied-confirmation`
);
return (
<div className={wrapperClasses}>
{this.state.copied && (
<span className={copiedConfirmationClasses}>Copied!</span>
)}
<Button variant="icon" onClick={onClickCopy} size="small" iconStroke>
{copyButtonValue}
</Button>
{this.props.enableShowSecret && this.renderShowSecretButton()}
</div>
);
};
render() {
const {
readOnly,
disabled,
error,
inputClassName,
inputOptions,
inputWrapperClass,
name,
onFocus,
onBlur,
placeholder,
type,
blockAutoComplete,
value,
ignore1password,
enableCopy,
enableShowSecret,
step,
min,
max,
} = this.props;
const { onInputChange } = this;
const shouldShowPasswordClass =
type === "password" && !this.state.showSecret;
const inputClasses = classnames(baseClass, inputClassName, {
[`${baseClass}--password`]: shouldShowPasswordClass,
[`${baseClass}--read-only`]: readOnly || disabled,
[`${baseClass}--disabled`]: disabled,
[`${baseClass}--error`]: error,
[`${baseClass}__textarea`]: type === "textarea",
});
const inputWrapperClasses = classnames(inputWrapperClass, {
[`input-field--read-only`]: readOnly || disabled,
[`input-field--disabled`]: disabled,
});
const formFieldProps = pick(this.props, [
"helpText",
"label",
"error",
"name",
"tooltip",
"labelTooltipPosition",
]);
const inputContainerClasses = classnames(`${baseClass}__input-container`, {
"copy-enabled": enableCopy,
});
if (type === "textarea") {
return (
<FormField
{...formFieldProps}
type="textarea"
className={inputWrapperClasses}
>
<div className={inputContainerClasses}>
<textarea
name={name}
id={name}
onChange={onInputChange}
onBlur={onBlur}
onFocus={onFocus}
className={inputClasses}
disabled={readOnly || disabled}
placeholder={placeholder}
ref={(r) => {
this.input = r;
}}
type={type}
{...inputOptions}
value={value}
/>
{enableCopy && this.renderCopyButton()}
</div>
</FormField>
);
}
const inputType = this.state.showSecret ? "text" : type;
return (
<FormField
{...formFieldProps}
type="input"
className={inputWrapperClasses}
>
<div className={inputContainerClasses}>
<input
disabled={readOnly || disabled}
name={name}
id={name}
onChange={onInputChange}
onFocus={onFocus}
onBlur={onBlur}
className={inputClasses}
placeholder={placeholder}
ref={(r) => {
this.input = r;
}}
type={inputType}
{...inputOptions}
value={value}
autoComplete={blockAutoComplete ? "new-password" : ""}
data-1p-ignore={ignore1password}
step={step}
min={min}
max={max}
/>
{enableCopy && this.renderCopyButton()}
</div>
</FormField>
);
}
}
export default InputField;

View file

@ -1,124 +0,0 @@
import React from "react";
import InputField from ".";
export default {
component: InputField,
title: "Components/FormFields/InputField",
argTypes: {
type: {
control: "select",
options: ["text", "password", "email", "number", "textarea"],
},
value: {
control: "text",
},
placeholder: {
control: "text",
},
label: {
control: "text",
},
error: {
control: "text",
},
helpText: {
control: "text",
},
disabled: {
control: "boolean",
},
readOnly: {
control: "boolean",
},
autofocus: {
control: "boolean",
},
enableCopy: {
control: "boolean",
},
enableShowSecret: {
control: "boolean",
},
},
};
const Template = (args) => <InputField {...args} />;
export const Basic = Template.bind({});
Basic.args = {
name: "basic-input",
label: "Basic Input",
value: "",
placeholder: "Enter text here",
};
export const WithValue = Template.bind({});
WithValue.args = {
...Basic.args,
value: "Sample text",
};
export const WithError = Template.bind({});
WithError.args = {
...Basic.args,
error: "This field is required",
};
export const Disabled = Template.bind({});
Disabled.args = {
...Basic.args,
disabled: true,
};
export const ReadOnly = Template.bind({});
ReadOnly.args = {
...Basic.args,
readOnly: true,
value: "Read-only content",
};
export const WithHelpText = Template.bind({});
WithHelpText.args = {
...Basic.args,
helpText: "This is some helpful information about the input field.",
};
export const Password = Template.bind({});
Password.args = {
...Basic.args,
type: "password",
label: "Password",
placeholder: "Enter your password",
};
export const Textarea = Template.bind({});
Textarea.args = {
...Basic.args,
type: "textarea",
label: "Text area",
placeholder: "Enter multiple lines of text",
};
export const WithCopyEnabled = Template.bind({});
WithCopyEnabled.args = {
...Basic.args,
enableCopy: true,
value: "This text can be copied",
};
export const WithCopyEnabledInput = Template.bind({});
WithCopyEnabledInput.args = {
...WithCopyEnabled.args,
};
export const WithTooltip = Template.bind({});
WithTooltip.args = {
...Basic.args,
tooltip: "This is a tooltip for the input field",
};
export const AutoFocus = Template.bind({});
AutoFocus.args = {
...Basic.args,
autofocus: true,
};

View file

@ -1,7 +1,6 @@
import { Meta, StoryObj } from "@storybook/react";
import { action } from "@storybook/addon-actions";
// @ts-ignore
import InputField from ".";
import "../../../../index.scss";
@ -20,10 +19,6 @@ const meta: Meta<typeof InputField> = {
blockAutoComplete: { control: "boolean" },
enableCopy: { control: "boolean" },
enableShowSecret: { control: "boolean" },
copyButtonPosition: {
control: "radio",
options: ["inside", "outside"],
},
labelTooltipPosition: {
control: "select",
options: ["top", "right", "bottom", "left"],
@ -47,6 +42,15 @@ export const Default: Story = {
},
};
export const WithValue: Story = {
args: {
...Default.args,
name: "with-value-input",
label: "Input with Value",
value: "Sample text",
},
};
export const WithError: Story = {
args: {
...Default.args,
@ -116,6 +120,15 @@ export const WithCopyButton: Story = {
},
};
export const AutoFocus: Story = {
args: {
...Default.args,
name: "autofocus-input",
label: "Autofocus Input",
autofocus: true,
},
};
export const Textarea: Story = {
args: {
...Default.args,

View file

@ -1,8 +1,8 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithSetup } from "test/test-utils";
// @ts-ignore
import InputField from "./InputField";
describe("InputField Component", () => {
@ -161,4 +161,208 @@ describe("InputField Component", () => {
expect(screen.getByPlaceholderText(/enter text/i)).toBeDisabled();
});
test("calls onChange with { name, value } when parseTarget is true", async () => {
render(
<InputField
value=""
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="my-field"
parseTarget
/>
);
await userEvent.type(screen.getByPlaceholderText(/enter text/i), "A");
expect(mockOnChange).toHaveBeenCalledTimes(1);
expect(mockOnChange).toHaveBeenCalledWith({
name: "my-field",
value: "A",
});
});
test("renders as read-only when readOnly prop is true", () => {
render(
<InputField
value="read only value"
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
readOnly
/>
);
expect(screen.getByPlaceholderText(/enter text/i)).toBeDisabled();
});
test("auto-focuses the input when autofocus is true", () => {
render(
<InputField
value=""
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
autofocus
/>
);
expect(screen.getByPlaceholderText(/enter text/i)).toHaveFocus();
});
test("sets autocomplete to 'new-password' when blockAutoComplete is true", () => {
render(
<InputField
value=""
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
blockAutoComplete
/>
);
expect(screen.getByPlaceholderText(/enter text/i)).toHaveAttribute(
"autocomplete",
"new-password"
);
});
test("sets data-1p-ignore when ignore1password is true", () => {
render(
<InputField
value=""
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
ignore1password
/>
);
expect(screen.getByPlaceholderText(/enter text/i)).toHaveAttribute(
"data-1p-ignore",
"true"
);
});
test("renders tooltip on label hover", async () => {
const { user } = renderWithSetup(
<InputField
value=""
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
tooltip="Helpful tooltip text"
/>
);
await user.hover(screen.getByText(/test input/i));
await waitFor(() => {
expect(screen.getByText("Helpful tooltip text")).toBeInTheDocument();
});
});
test("sets step, min, and max attributes on number input", () => {
render(
<InputField
value={5}
onChange={mockOnChange}
label="Number Input"
placeholder="Enter number"
name="test-number"
type="number"
step={0.5}
min={0}
max={100}
/>
);
const input = screen.getByPlaceholderText(/enter number/i);
expect(input).toHaveAttribute("step", "0.5");
expect(input).toHaveAttribute("min", "0");
expect(input).toHaveAttribute("max", "100");
});
test("copies value to clipboard when copy button is clicked", async () => {
const writeTextMock = jest.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, "clipboard", {
value: { writeText: writeTextMock },
writable: true,
configurable: true,
});
render(
<InputField
value="Copy me"
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
enableCopy
/>
);
const copyButton = screen.getByTestId("copy-icon").closest("button")!;
await userEvent.click(copyButton);
expect(writeTextMock).toHaveBeenCalledWith("Copy me");
await waitFor(() => {
expect(screen.getByText("Copied!")).toBeInTheDocument();
});
});
test("renders show-secret eye toggle with enableCopy and enableShowSecret on password field", async () => {
render(
<InputField
value="s3cret"
onChange={mockOnChange}
label="Password"
placeholder="Enter password"
name="test-password"
type="password"
enableCopy
enableShowSecret
/>
);
// The eye icon should be present
const eyeIcon = screen.getByTestId("eye-icon");
expect(eyeIcon).toBeInTheDocument();
// Initially the input type should be password
const input = screen.getByPlaceholderText(/enter password/i);
expect(input).toHaveAttribute("type", "password");
// Click the eye toggle to reveal the secret
const eyeButton = eyeIcon.closest("button")!;
await userEvent.click(eyeButton);
// After toggling, the input type should be text
expect(input).toHaveAttribute("type", "text");
// Click again to hide
await userEvent.click(eyeButton);
expect(input).toHaveAttribute("type", "password");
});
test("renders copy button in textarea mode when enableCopy is true", () => {
render(
<InputField
value="Textarea content"
onChange={mockOnChange}
label="Test Textarea"
placeholder="Enter text"
name="test-textarea"
type="textarea"
enableCopy
/>
);
expect(screen.getByTestId("copy-icon")).toBeInTheDocument();
});
});

View file

@ -0,0 +1,257 @@
import React, { useState, useRef, useEffect, useCallback } from "react";
import classnames from "classnames";
import { PlacesType } from "react-tooltip-5";
import { stringToClipboard } from "utilities/copy_text";
import FormField from "components/forms/FormField";
import Button from "components/buttons/Button";
import Icon from "components/Icon";
const baseClass = "input-field";
export interface IInputFieldProps {
autofocus?: boolean;
/** readOnly displays a non-editable field */
readOnly?: boolean;
/** disabled displays a greyed out non-editable field */
disabled?: boolean;
error?: string | null;
inputClassName?: string;
inputWrapperClass?: string;
inputOptions?: React.InputHTMLAttributes<HTMLInputElement>;
name?: string;
/**
* Receives the field value (string) by default, or { name, value } when
* parseTarget is true. See IInputFieldParseTarget and InputFieldOnChange
* in interfaces/form_field.ts for caller-side typing helpers.
*/
onChange?: (value: any) => void;
onBlur?: (
evt: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>
) => void;
onFocus?: (
evt: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>
) => void;
placeholder?: string;
type?: string;
blockAutoComplete?: boolean;
value?: boolean | string | number | null;
/** Returns both name and value */
parseTarget?: boolean;
tooltip?: React.ReactNode;
labelTooltipPosition?: PlacesType;
label?: React.ReactNode;
labelClassName?: string;
helpText?: React.ReactNode;
/** Use in conjunction with type "password" and enableCopy to see eye icon to view */
enableShowSecret?: boolean;
enableCopy?: boolean;
ignore1password?: boolean;
/** Only effective on input type number */
step?: string | number;
/** Only effective on input type number */
min?: string | number;
/** Only effective on input type number */
max?: string | number;
}
const InputField = ({
autofocus = false,
readOnly,
disabled,
error,
inputClassName,
inputWrapperClass = "",
inputOptions = {},
name,
onChange,
onBlur,
onFocus,
placeholder,
type = "text",
blockAutoComplete = false,
value = "",
parseTarget = false,
tooltip = "",
labelTooltipPosition,
label = null,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
labelClassName: _labelClassName = "",
helpText = "",
enableShowSecret = false,
enableCopy = false,
ignore1password = false,
step,
min,
max,
}: IInputFieldProps): JSX.Element => {
const [copied, setCopied] = useState(false);
const [showSecret, setShowSecret] = useState(false);
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);
useEffect(() => {
if (autofocus && inputRef.current) {
(inputRef.current as HTMLElement).focus();
}
}, [autofocus]);
const onInputChange = useCallback(
(evt: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
evt.preventDefault();
const target = evt.target as HTMLInputElement;
const { value: inputValue, name: inputName } = target;
if (parseTarget) {
// Returns both name and value
return onChange?.({ value: inputValue, name: inputName });
}
return onChange?.(inputValue);
},
[onChange, parseTarget]
);
const onToggleSecret = useCallback((evt: React.MouseEvent) => {
evt.preventDefault();
setShowSecret((prev) => !prev);
}, []);
const onClickCopy = useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
stringToClipboard(value).then(() => {
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 2000);
});
},
[value]
);
const renderShowSecretButton = () => {
return (
<Button
variant="icon"
className={`${baseClass}__show-secret-icon`}
onClick={onToggleSecret}
size="small"
>
<Icon name="eye" />
</Button>
);
};
const renderCopyButton = () => {
const copyButtonValue = <Icon name="copy" />;
const wrapperClasses = classnames(`${baseClass}__copy-wrapper`, {
[`${baseClass}__copy-wrapper__text-area`]: type === "textarea",
});
const copiedConfirmationClasses = classnames(
`${baseClass}__copied-confirmation`
);
return (
<div className={wrapperClasses}>
{copied && <span className={copiedConfirmationClasses}>Copied!</span>}
<Button variant="icon" onClick={onClickCopy} size="small" iconStroke>
{copyButtonValue}
</Button>
{enableShowSecret && renderShowSecretButton()}
</div>
);
};
const shouldShowPasswordClass = type === "password" && !showSecret;
const inputClasses = classnames(baseClass, inputClassName, {
[`${baseClass}--password`]: shouldShowPasswordClass,
[`${baseClass}--read-only`]: readOnly || disabled,
[`${baseClass}--disabled`]: disabled,
[`${baseClass}--error`]: !!error,
[`${baseClass}__textarea`]: type === "textarea",
});
const inputWrapperClasses = classnames(inputWrapperClass, {
[`input-field--read-only`]: readOnly || disabled,
[`input-field--disabled`]: disabled,
});
const formFieldProps = {
helpText,
label,
error,
name: name ?? "",
tooltip,
labelTooltipPosition,
};
const inputContainerClasses = classnames(`${baseClass}__input-container`, {
"copy-enabled": enableCopy,
});
if (type === "textarea") {
return (
<FormField
{...formFieldProps}
type="textarea"
className={inputWrapperClasses}
>
<div className={inputContainerClasses}>
<textarea
name={name}
id={name}
onChange={onInputChange}
onBlur={onBlur}
onFocus={onFocus}
className={inputClasses}
disabled={readOnly || disabled}
placeholder={placeholder}
ref={(r) => {
inputRef.current = r;
}}
{...(inputOptions as React.TextareaHTMLAttributes<HTMLTextAreaElement>)}
value={value as string | number}
/>
{enableCopy && renderCopyButton()}
</div>
</FormField>
);
}
const inputType = showSecret ? "text" : type;
return (
<FormField {...formFieldProps} type="input" className={inputWrapperClasses}>
<div className={inputContainerClasses}>
<input
disabled={readOnly || disabled}
name={name}
id={name}
onChange={onInputChange}
onFocus={onFocus}
onBlur={onBlur}
className={inputClasses}
placeholder={placeholder}
ref={(r) => {
inputRef.current = r;
}}
type={inputType}
{...inputOptions}
value={value as string | number}
autoComplete={blockAutoComplete ? "new-password" : ""}
data-1p-ignore={ignore1password}
step={step}
min={min}
max={max}
/>
{enableCopy && renderCopyButton()}
</div>
</FormField>
);
};
export default InputField;

View file

@ -1,6 +1,5 @@
import React from "react";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import classnames from "classnames";

View file

@ -1,155 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import classnames from "classnames";
import { ICON_MAP } from "components/icons";
import Icon from "components/Icon/Icon";
import TooltipWrapper from "components/TooltipWrapper";
import Button from "components/buttons/Button";
import InputField from "../InputField";
const baseClass = "input-icon-field";
class InputFieldWithIcon extends InputField {
static propTypes = {
autofocus: PropTypes.bool,
error: PropTypes.string,
helpText: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
iconSvg: PropTypes.oneOf(Object.keys(ICON_MAP)),
label: PropTypes.string,
name: PropTypes.string,
onChange: PropTypes.func,
onClick: PropTypes.func,
clearButton: PropTypes.func,
placeholder: PropTypes.string,
tabIndex: PropTypes.number,
type: PropTypes.string,
className: PropTypes.string,
disabled: PropTypes.bool,
inputOptions: PropTypes.object, // eslint-disable-line react/forbid-prop-types
tooltip: PropTypes.string,
ignore1Password: PropTypes.bool,
};
renderHeading = () => {
const { error, placeholder, name, tooltip, disabled } = this.props;
const label = this.props.label ?? placeholder;
const labelClasses = classnames(`${baseClass}__label`, {
[`${baseClass}__errors`]: !!error,
[`${baseClass}__label--disabled`]: disabled,
});
return (
<label
htmlFor={name}
className={labelClasses}
data-has-tooltip={!!tooltip}
>
{tooltip && !error ? (
<TooltipWrapper position="bottom-start" tipContent={tooltip}>
{label}
</TooltipWrapper>
) : (
<>{error || label}</>
)}
</label>
);
};
renderHelpText = () => {
const { helpText } = this.props;
if (helpText) {
return (
<span className={`${baseClass}__help-text form-field__help-text`}>
{helpText}
</span>
);
}
return false;
};
render() {
const {
className,
error,
iconSvg,
name,
placeholder,
tabIndex,
type,
value,
disabled,
inputOptions,
ignore1Password,
onClick,
onChange,
clearButton,
} = this.props;
const { onInputChange, renderHelpText } = this;
const wrapperClasses = classnames(baseClass, "form-field");
const inputClasses = classnames(
`${baseClass}__input`,
className,
{ "input-with-icon": !!iconSvg },
{ [`${baseClass}__input--error`]: error },
{ [`${baseClass}__input--password`]: type === "password" && value }
);
const inputWrapperClasses = classnames(`${baseClass}__input-wrapper`, {
[`${baseClass}__input-wrapper--disabled`]: disabled,
});
const iconClasses = classnames(
`${baseClass}__icon`,
{ [`${baseClass}__icon--error`]: error },
{ [`${baseClass}__icon--active`]: value }
);
const handleClear = () => {
onChange("");
};
return (
<div className={wrapperClasses}>
{this.props.label && this.renderHeading()}
<div className={inputWrapperClasses}>
<input
id={name}
name={name}
onChange={onInputChange}
onClick={onClick}
className={inputClasses}
placeholder={placeholder}
ref={(r) => {
this.input = r;
}}
tabIndex={tabIndex}
type={type}
value={value}
disabled={disabled}
{...inputOptions}
data-1p-ignore={ignore1Password}
/>
{iconSvg && <Icon name={iconSvg} className={iconClasses} />}
{clearButton && !!value && (
<Button
onClick={() => handleClear()}
variant="icon"
className={`${baseClass}__clear-button`}
>
<Icon name="close-filled" color="core-fleet-black" />
</Button>
)}
</div>
{renderHelpText()}
</div>
);
}
}
export default InputFieldWithIcon;

View file

@ -1,7 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import { action } from "@storybook/addon-actions";
// @ts-ignore
import InputFieldWithIcon from ".";
// Define metadata for the story
@ -95,7 +94,7 @@ export const WithTooltip: Story = {
export const WithClearButton: Story = {
args: {
...Default.args,
clearButton: action("clearButton"),
clearButton: true,
value: "example@email.com",
},
};

View file

@ -2,7 +2,6 @@ import React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithSetup } from "test/test-utils";
// @ts-ignore
import InputFieldWithIcon from "./InputFieldWithIcon";
describe("InputFieldWithIcon Component", () => {
@ -129,4 +128,166 @@ describe("InputFieldWithIcon Component", () => {
expect(tooltip).toBeInTheDocument();
});
});
test("renders icon when iconSvg is provided", () => {
render(
<InputFieldWithIcon
value=""
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
iconSvg="search"
/>
);
expect(screen.getByTestId("search-icon")).toBeInTheDocument();
});
test("does not render icon when iconSvg is not provided", () => {
render(
<InputFieldWithIcon
value=""
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
/>
);
expect(screen.queryByTestId("search-icon")).not.toBeInTheDocument();
});
test("renders as disabled when disabled prop is true", () => {
render(
<InputFieldWithIcon
value=""
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
disabled
/>
);
expect(screen.getByPlaceholderText(/enter text/i)).toBeDisabled();
});
test("does not allow typing when disabled", async () => {
render(
<InputFieldWithIcon
value=""
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
disabled
/>
);
await userEvent.type(
screen.getByPlaceholderText(/enter text/i),
"some text"
);
expect(mockOnChange).not.toHaveBeenCalled();
});
test("autofocuses the input when autofocus is true", () => {
render(
<InputFieldWithIcon
value=""
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
autofocus
/>
);
expect(screen.getByPlaceholderText(/enter text/i)).toHaveFocus();
});
test("does not autofocus the input when autofocus is false", () => {
render(
<InputFieldWithIcon
value=""
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
/>
);
expect(screen.getByPlaceholderText(/enter text/i)).not.toHaveFocus();
});
test("calls onClick when the input is clicked", async () => {
const mockOnClick = jest.fn();
render(
<InputFieldWithIcon
value=""
onChange={mockOnChange}
onClick={mockOnClick}
label="Test Input"
placeholder="Enter text"
name="test-input"
/>
);
await userEvent.click(screen.getByPlaceholderText(/enter text/i));
expect(mockOnClick).toHaveBeenCalledTimes(1);
});
test("sets data-1p-ignore attribute when ignore1Password is true", () => {
render(
<InputFieldWithIcon
value=""
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
ignore1Password
/>
);
expect(screen.getByPlaceholderText(/enter text/i)).toHaveAttribute(
"data-1p-ignore",
"true"
);
});
test("does not render clear button when value is empty", () => {
render(
<InputFieldWithIcon
value=""
onChange={mockOnChange}
label="Test Input"
placeholder="Enter text"
name="test-input"
clearButton
/>
);
expect(screen.queryByRole("button")).not.toBeInTheDocument();
});
test("applies password type styling when type is password and value is present", () => {
render(
<InputFieldWithIcon
value="secret"
onChange={mockOnChange}
label="Password"
placeholder="Enter password"
name="test-password"
type="password"
/>
);
const input = screen.getByPlaceholderText(/enter password/i);
expect(input).toHaveAttribute("type", "password");
expect(input).toHaveClass("input-icon-field__input--password");
});
});

View file

@ -0,0 +1,165 @@
import React, { useCallback, useEffect, useRef } from "react";
import classnames from "classnames";
import { ICON_MAP } from "components/icons";
import Icon from "components/Icon/Icon";
import TooltipWrapper from "components/TooltipWrapper";
import Button from "components/buttons/Button";
const baseClass = "input-icon-field";
type IconName = keyof typeof ICON_MAP;
export interface IInputFieldWithIconProps {
autofocus?: boolean;
error?: string | null;
helpText?: string[] | string;
iconSvg?: IconName;
label?: string;
name?: string;
onChange?: (value: any) => void;
onClick?: (evt: React.MouseEvent<HTMLInputElement>) => void;
clearButton?: boolean;
placeholder?: string;
tabIndex?: number;
type?: string;
className?: string;
disabled?: boolean;
inputOptions?: React.InputHTMLAttributes<HTMLInputElement>;
tooltip?: string;
ignore1Password?: boolean;
value?: string;
}
const InputFieldWithIcon = ({
autofocus = false,
error,
helpText,
iconSvg,
label,
name,
onChange,
onClick,
clearButton,
placeholder,
tabIndex,
type,
className,
disabled,
inputOptions,
tooltip,
ignore1Password,
value,
}: IInputFieldWithIconProps): JSX.Element => {
const inputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
if (autofocus && inputRef.current) {
inputRef.current.focus();
}
}, [autofocus]);
const onInputChange = useCallback(
(evt: React.ChangeEvent<HTMLInputElement>) => {
evt.preventDefault();
return onChange?.(evt.target.value);
},
[onChange]
);
const renderHeading = () => {
const labelClasses = classnames(`${baseClass}__label`, {
[`${baseClass}__errors`]: !!error,
[`${baseClass}__label--disabled`]: disabled,
});
return (
<label
htmlFor={name}
className={labelClasses}
data-has-tooltip={!!tooltip}
>
{tooltip && !error ? (
<TooltipWrapper position="bottom-start" tipContent={tooltip}>
{label}
</TooltipWrapper>
) : (
<>{error || label}</>
)}
</label>
);
};
const renderHelpText = () => {
if (helpText) {
return (
<span className={`${baseClass}__help-text form-field__help-text`}>
{helpText}
</span>
);
}
return false;
};
const wrapperClasses = classnames(baseClass, "form-field");
const inputClasses = classnames(
`${baseClass}__input`,
className,
{ "input-with-icon": !!iconSvg },
{ [`${baseClass}__input--error`]: !!error },
{ [`${baseClass}__input--password`]: !!(type === "password" && value) }
);
const inputWrapperClasses = classnames(`${baseClass}__input-wrapper`, {
[`${baseClass}__input-wrapper--disabled`]: disabled,
});
const iconClasses = classnames(
`${baseClass}__icon`,
{ [`${baseClass}__icon--error`]: !!error },
{ [`${baseClass}__icon--active`]: !!value }
);
const handleClear = () => {
onChange?.("");
};
return (
<div className={wrapperClasses}>
{label && renderHeading()}
<div className={inputWrapperClasses}>
<input
id={name}
name={name}
onChange={onInputChange}
onClick={onClick}
className={inputClasses}
placeholder={placeholder}
ref={inputRef}
tabIndex={tabIndex}
type={type}
value={value}
disabled={disabled}
{...inputOptions}
data-1p-ignore={ignore1Password}
/>
{iconSvg && <Icon name={iconSvg} className={iconClasses} />}
{clearButton && !!value && (
<Button
onClick={() => handleClear()}
variant="icon"
className={`${baseClass}__clear-button`}
>
<Icon name="close-filled" color="core-fleet-black" />
</Button>
)}
</div>
{renderHelpText()}
</div>
);
};
export default InputFieldWithIcon;

View file

@ -59,7 +59,6 @@ const SearchField = ({
onChange={onInputChange}
onClick={onClick}
clearButton={clearButton}
iconPosition="start"
iconSvg={icon}
disabled={disabled}
/>

View file

@ -6,7 +6,6 @@ import Button from "components/buttons/Button";
import { IQuery } from "interfaces/query";
import { IScheduledQuery } from "interfaces/scheduled_query";
import { ITarget, ITargetsAPIResponse } from "interfaces/target";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
// @ts-ignore
import SelectTargetsDropdown from "components/forms/fields/SelectTargetsDropdown";

View file

@ -7,7 +7,6 @@ import { ITarget, ITargetsAPIResponse } from "interfaces/target";
import { IEditPackFormData } from "interfaces/pack";
import PATHS from "router/paths";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import BackButton from "components/BackButton";
// @ts-ignore

View file

@ -5,7 +5,6 @@ import { IWebhookActivities } from "interfaces/webhook";
import Modal from "components/Modal";
import validURL from "components/forms/validators/valid_url";
import Slider from "components/forms/fields/Slider";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import RevealButton from "components/buttons/RevealButton";

View file

@ -9,7 +9,6 @@ import paths from "router/paths";
import { NotificationContext } from "context/notification";
import certificatesAPI, { ICertificate } from "services/entities/certificates";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import Modal from "components/Modal";

View file

@ -9,7 +9,6 @@ import configAPI from "services/entities/config";
import teamsAPI from "services/entities/teams";
import { ApplePlatform } from "interfaces/platform";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import Checkbox from "components/forms/fields/Checkbox";

View file

@ -9,7 +9,6 @@ import { NotificationContext } from "context/notification";
import configAPI from "services/entities/config";
import teamsAPI from "services/entities/teams";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper";

View file

@ -4,7 +4,6 @@ import Button from "components/buttons/Button";
import { ISecretPayload } from "interfaces/secrets";
import secretsAPI from "services/entities/secrets";
import { NotificationContext } from "context/notification";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import { validateFormData, IAddSecretModalFormValidation } from "./helpers";

View file

@ -27,7 +27,6 @@ import {
getTargetType,
} from "pages/SoftwarePage/helpers";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";

View file

@ -19,7 +19,6 @@ import softwareAPI from "services/entities/software";
import Modal from "components/Modal";
import ModalFooter from "components/ModalFooter";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import FileUploader from "components/FileUploader";
import TabNav from "components/TabNav";

View file

@ -8,7 +8,6 @@ import { ISoftwarePackage } from "interfaces/software";
import Modal from "components/Modal";
import Button from "components/buttons/Button";
import CustomLink from "components/CustomLink";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Editor from "components/Editor";

View file

@ -7,7 +7,6 @@ import { IAppStoreApp } from "interfaces/software";
import { IInputFieldParseTarget } from "interfaces/form_field";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import CustomLink from "components/CustomLink";
import Button from "components/buttons/Button";
@ -110,7 +109,7 @@ const SoftwareAndroidForm = ({
<>
<div className={`${baseClass}__form-fields`}>
<InputField
autoFocus
autofocus
label="Application ID"
placeholder="com.android.chrome"
helpText={

View file

@ -34,7 +34,6 @@ import Modal from "components/Modal";
import Button from "components/buttons/Button";
import Slider from "components/forms/fields/Slider";
import Radio from "components/forms/fields/Radio";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import CustomLink from "components/CustomLink";
import validUrl from "components/forms/validators/valid_url";

View file

@ -9,7 +9,6 @@ import Modal from "components/Modal";
import Button from "components/buttons/Button";
import Slider from "components/forms/fields/Slider";
import Checkbox from "components/forms/fields/Checkbox";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import TooltipWrapper from "components/TooltipWrapper";
import {

View file

@ -8,7 +8,6 @@ import configAPI from "services/entities/config";
import paths from "router/paths";
import { UNCHANGED_PASSWORD_API_RESPONSE } from "utilities/constants";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import CustomLink from "components/CustomLink";

View file

@ -2,7 +2,6 @@ import React, { useMemo } from "react";
import { ICertificateAuthorityPartial } from "interfaces/certificates";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import TooltipWrapper from "components/TooltipWrapper";

View file

@ -2,7 +2,6 @@ import React, { useMemo, useState } from "react";
import { ICertificateAuthorityPartial } from "interfaces/certificates";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import TooltipWrapper from "components/TooltipWrapper";

View file

@ -2,7 +2,6 @@ import React, { useMemo, useState } from "react";
import { ICertificateAuthorityPartial } from "interfaces/certificates";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import CustomLink from "components/CustomLink";

View file

@ -2,7 +2,6 @@ import React, { useMemo, useState } from "react";
import { ICertificateAuthorityPartial } from "interfaces/certificates";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import TooltipWrapper from "components/TooltipWrapper";

View file

@ -1,6 +1,5 @@
import React, { useState } from "react";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import TooltipWrapper from "components/TooltipWrapper";

View file

@ -2,7 +2,6 @@ import React, { useMemo } from "react";
import { ICertificateAuthorityPartial } from "interfaces/certificates";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import TooltipWrapper from "components/TooltipWrapper";

View file

@ -13,7 +13,6 @@ import { IConfig } from "interfaces/config";
import { IInputFieldParseTarget } from "interfaces/form_field";
import { getErrorReason } from "interfaces/errors";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Checkbox from "components/forms/fields/Checkbox";
import validUrl from "components/forms/validators/valid_url";

View file

@ -4,7 +4,6 @@ import { size } from "lodash";
import { NotificationContext } from "context/notification";
import conditionalAccessAPI from "services/entities/conditional_access";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import CustomLink from "components/CustomLink";
import Modal from "components/Modal";

View file

@ -8,7 +8,6 @@ import configAPI from "services/entities/config";
import conditionalAccessAPI from "services/entities/conditional_access";
import { IConfig } from "interfaces/config";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import CustomLink from "components/CustomLink";
import Modal from "components/Modal";

View file

@ -15,7 +15,6 @@ import Button from "components/buttons/Button";
import Checkbox from "components/forms/fields/Checkbox";
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import validUrl from "components/forms/validators/valid_url";
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper";

View file

@ -11,7 +11,6 @@ import configAPI from "services/entities/config";
import { NotificationContext } from "context/notification";
import { AppContext } from "context/app";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button/Button";
import TooltipWrapper from "components/TooltipWrapper";

View file

@ -9,7 +9,6 @@ import {
} from "interfaces/integration";
import Button from "components/buttons/Button";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import validUrl from "components/forms/validators/valid_url";

View file

@ -3,7 +3,6 @@ import React, { useContext, useState, useRef } from "react";
import PATHS from "router/paths";
import { AppContext } from "context/app";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import BackButton from "components/BackButton";
import MainContent from "components/MainContent";

View file

@ -4,7 +4,6 @@ import { NotificationContext } from "context/notification";
import { AppContext } from "context/app";
import configAPI from "services/entities/config";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Modal from "components/Modal";
import Button from "components/buttons/Button";

View file

@ -15,7 +15,6 @@ import configAPI from "services/entities/config";
import SettingsSection from "pages/admin/components/SettingsSection";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Radio from "components/forms/fields/Radio/Radio";
import Slider from "components/forms/fields/Slider/Slider";
@ -220,7 +219,7 @@ const EndUserMigrationSection = ({ router }: IEndUserMigrationSectionProps) => {
label="Webhook URL"
value={formData.webhookUrl}
onChange={onChangeWebhookUrl}
error={!isValidWebhookUrl && "Must be a valid URL."}
error={!isValidWebhookUrl ? "Must be a valid URL." : undefined}
helpText={
<>
When the end users clicks <b>Start</b>, a JSON payload is sent

View file

@ -7,7 +7,6 @@ import PageDescription from "components/PageDescription";
import Button from "components/buttons/Button";
import Checkbox from "components/forms/fields/Checkbox";
import CustomLink from "components/CustomLink";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import validUrl from "components/forms/validators/valid_url";
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper";

View file

@ -7,7 +7,6 @@ import SettingsSection from "pages/admin/components/SettingsSection";
import PageDescription from "components/PageDescription";
import Button from "components/buttons/Button";
import Checkbox from "components/forms/fields/Checkbox";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";

View file

@ -5,7 +5,6 @@ import { IInputFieldParseTarget } from "interfaces/form_field";
import SettingsSection from "pages/admin/components/SettingsSection";
import PageDescription from "components/PageDescription";
import Button from "components/buttons/Button";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import validUrl from "components/forms/validators/valid_url";
import validHostname from "components/forms/validators/valid_hostname";

View file

@ -7,7 +7,6 @@ import SettingsSection from "pages/admin/components/SettingsSection";
import PageDescription from "components/PageDescription";
import Button from "components/buttons/Button";
import CustomLink from "components/CustomLink";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
// @ts-ignore
import OrgLogoIcon from "components/icons/OrgLogoIcon";

View file

@ -10,7 +10,6 @@ import Button from "components/buttons/Button";
import Checkbox from "components/forms/fields/Checkbox";
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
// @ts-ignore
import validEmail from "components/forms/validators/valid_email";

View file

@ -5,7 +5,6 @@ import { IInputFieldParseTarget } from "interfaces/form_field";
import SettingsSection from "pages/admin/components/SettingsSection";
import Button from "components/buttons/Button";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import validUrl from "components/forms/validators/valid_url";
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper";

View file

@ -29,7 +29,6 @@ import validURL from "components/forms/validators/valid_url";
import Button from "components/buttons/Button";
import DataError from "components/DataError";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Spinner from "components/Spinner";
import SectionHeader from "components/SectionHeader";

View file

@ -5,7 +5,6 @@ import { ITeamFormData } from "services/entities/teams";
import Modal from "components/Modal";
import Button from "components/buttons/Button";
import InfoBanner from "components/InfoBanner/InfoBanner";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
const baseClass = "create-team-modal";

View file

@ -3,7 +3,6 @@ import React, { useState, useCallback, useEffect } from "react";
import { ITeamFormData } from "services/entities/teams";
import Modal from "components/Modal";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";

View file

@ -19,7 +19,6 @@ import validatePresence from "components/forms/validators/validate_presence";
import validEmail from "components/forms/validators/valid_email";
// @ts-ignore
import validPassword from "components/forms/validators/valid_password";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Checkbox from "components/forms/fields/Checkbox";
import Radio from "components/forms/fields/Radio";
@ -504,7 +503,7 @@ const UserForm = ({
placeholder="Full name"
value={formData.name || ""}
inputOptions={{
maxLength: "80",
maxLength: 80,
}}
ignore1password
parseTarget

View file

@ -7,7 +7,6 @@ import classnames from "classnames";
import { getPathWithQueryParams } from "utilities/url";
import Radio from "components/forms/fields/Radio";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import TooltipWrapper from "components/TooltipWrapper";
import CustomLink from "components/CustomLink";

View file

@ -1,6 +1,5 @@
import React from "react";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";

View file

@ -201,7 +201,6 @@ const SelectQueryModal = ({
value={queriesFilter}
autofocus
iconSvg="search"
iconPosition="start"
/>
<div className={`${baseClass}__query-selection`}>{queryList}</div>
</>
@ -219,7 +218,6 @@ const SelectQueryModal = ({
value={queriesFilter}
autofocus
iconSvg="search"
iconPosition="start"
/>
</div>
<div className={`${baseClass}__no-queries`}>

View file

@ -2,7 +2,6 @@ import React, { useState } from "react";
import { IHostEndUser } from "interfaces/host";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import Modal from "components/Modal";

View file

@ -38,7 +38,6 @@ import SidePanelPage from "components/SidePanelPage";
import MainContent from "components/MainContent";
import SidePanelContent from "components/SidePanelContent";
import QuerySidePanel from "components/side_panels/QuerySidePanel";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";

View file

@ -3,7 +3,6 @@ import { renderWithSetup } from "test/test-utils";
import { screen, render } from "@testing-library/react";
import { noop } from "lodash";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import LabelForm from "./LabelForm";
@ -37,7 +36,9 @@ describe("LabelForm", () => {
onCancel={noop}
teamName={null}
immutableFields={[]}
additionalFields={<InputField name="test field" label="test field" />}
additionalFields={
<InputField name="test field" label="test field" value="" />
}
/>
);

View file

@ -1,6 +1,5 @@
import React, { ReactNode, useState } from "react";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper";
@ -122,6 +121,13 @@ const LabelForm = ({
setFormValidation(fullValidation);
};
const handleBlur = (
evt: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const target = evt.currentTarget as HTMLInputElement;
onInputBlur({ name: target.name, value: target.value });
};
const onSubmitForm = (evt: React.FormEvent) => {
evt.preventDefault();
@ -139,7 +145,7 @@ const LabelForm = ({
parseTarget
name="name"
onChange={onFormChange}
onBlur={onInputBlur}
onBlur={handleBlur}
value={name}
inputClassName={`${baseClass}__label-title`}
label="Name"
@ -150,7 +156,7 @@ const LabelForm = ({
parseTarget
name="description"
onChange={onFormChange}
onBlur={onInputBlur}
onBlur={handleBlur}
value={description}
inputClassName={`${baseClass}__label-description`}
label="Description"

View file

@ -6,7 +6,6 @@ import Modal from "components/Modal";
import Button from "components/buttons/Button";
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import { IQuery } from "interfaces/query";
import { IScheduledQuery } from "interfaces/scheduled_query";

View file

@ -10,7 +10,6 @@ import Button from "components/buttons/Button";
import RevealButton from "components/buttons/RevealButton";
import CustomLink from "components/CustomLink";
import Slider from "components/forms/fields/Slider";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Modal from "components/Modal";
import Icon from "components/Icon";

View file

@ -16,7 +16,6 @@ import Modal from "components/Modal";
import Slider from "components/forms/fields/Slider";
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Button from "components/buttons/Button";
import Radio from "components/forms/fields/Radio";

View file

@ -12,7 +12,6 @@ import { IPolicyFormData } from "interfaces/policy";
import { CommaSeparatedPlatformString } from "interfaces/platform";
import useDeepEffect from "hooks/useDeepEffect";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import Checkbox from "components/forms/fields/Checkbox";
import TooltipWrapper from "components/TooltipWrapper";

View file

@ -24,7 +24,6 @@ import {
} from "interfaces/team";
import Modal from "components/Modal";
import Button from "components/buttons/Button";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import TeamsDropdown from "components/TeamsDropdown";
import { useTeamIdParam } from "hooks/useTeamIdParam";

View file

@ -23,7 +23,6 @@ import {
} from "interfaces/schedulable_query";
import Checkbox from "components/forms/fields/Checkbox";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";