import React, { ReactElement, useCallback } from "react"; import classNames from "classnames"; import Label from "../Label"; import Icon from "react-oui-icons"; import ButtonIcon from "../ButtonIcon"; interface InputProps { /** CSS class names. */ className?: string, /** The default value of the input used on initial render */ defaultValue?: string; /** Includes search icon if true */ displayError?: boolean; /** Whether or not to automatically focus this input */ focus?: boolean; /** Whether or not to add a clear button to right of input */ hasClearButton?: boolean; /** Disables spell checking when set to false */ hasSpellCheck?: boolean; /** Id of the input to properly associate with the input's label */ id?: string; /** Allows for prevention of input autocomplete suggestions */ isAutoCompleteEnabled?: boolean; /** Prevents input from being modified and appears disabled */ isDisabled?: boolean; /** Includes error if true */ isFilter?: boolean; /** Adds an optional label if there is a label provided */ isOptional?: any; /** Prevents input from being modified but doesn't appear disabled */ isReadOnly?: boolean; /** Includes required asterisk label if true */ isRequired?: any; /** Text that describes the input */ label?: string; /** Name of the icon to place on left side of input */ leftIconName?: string; /** * Max value for the `input`. Should be used only when `type` is `number`. */ max?: number; /** * Max length of the input value. Should be used only when type is 'text', * 'email', 'search', 'password', 'tel', or 'url'. */ maxLength?: number; /** * Min value for the `input`. Should be used only when `type` is `number`. */ min?: number; /** Form note for the input */ note?: string | null; /** * Function that fires when the input loses focus. It fires regardless of * whether the value has changed. */ onBlur?: (...args: any[]) => any; /** Function that fires when the input loses focus after the value changes */ onChange?: (...args: any[]) => any; /** Function that fires when the input's clear button is clicked */ onClearButtonClick?: (...args: any[]) => any; /** Function that fires when the input is clicked */ onClick?: (...args: any[]) => any; /** Function that fires when the input gains focus */ onFocus?: (...args: any[]) => any; /** Function that fires anytime the input value changes */ onInput?: (...args: any[]) => any; /** Function that fires when a key is pressed down */ onKeyDown?: (...args: any[]) => any; /** Input placeholder text */ placeholder?: string; /** Name of the icon to place on right side of input */ rightIconName?: string; /** RightContainer on right side of input */ RightContainer?: any; /** Input step value */ step?: string; /** Hook for automated JavaScript tests */ testSection?: string; /** Align text inside input. Default is left. */ textAlign?: "left" | "right"; /** Supported input types */ type: "text" | "password" | "date" | "number" | "email" | "url" | "search" | "tel" | "time"; /** Text within the input */ value?: string | number; } const Input = React.forwardRef(({ className, defaultValue, displayError, focus, hasClearButton, hasSpellCheck, id, isAutoCompleteEnabled = true, isDisabled, isFilter, isOptional, isReadOnly, isRequired, label, leftIconName, max, maxLength, min, note, onBlur, onChange, onClearButtonClick, onClick, onFocus, onInput, onKeyDown, placeholder, rightIconName, RightContainer, step, testSection, textAlign, type, value, ...props }: InputProps, ref?: React.Ref) => { const renderNote = useCallback( () => (
{note}
), [note, testSection] ); const renderIcon = (iconName) : ReactElement => { return ; }; const renderClearButton = (): ReactElement => { return ( ); }; const renderInput = () : ReactElement => { let hasAlignStyle = false; if (textAlign) { hasAlignStyle = true; } const classes = classNames( "oui-text-input", { "oui-text-input--read-only": isReadOnly }, { "oui-text-input--search": isFilter }, { "oui-form-bad-news": displayError }, { [`text--${textAlign}`]: hasAlignStyle }, className, ); const input = ( ); if (leftIconName || !!RightContainer || rightIconName || hasClearButton) { const containerClasses = classNames("position--relative", { [`oui-text-input-with-icon--left`]: leftIconName, [`oui-text-input-with-icon--right`]: rightIconName, }); return (
{leftIconName && {renderIcon(leftIconName)}} {input} {rightIconName && !hasClearButton && ( {renderIcon(rightIconName)} )} {!!RightContainer && !hasClearButton && ( )} {hasClearButton && {renderClearButton()}}
); } return input; }; if (label) { return (
{renderInput()} {note && renderNote()}
); } return ( {renderInput()} {note && renderNote()} ); }); Input.propTypes = { /** Id to link label and input for accessibility reasons * @param {Object} props Object of props * @returns {Error} Error or null */ id: function verifyIDProp(props) { if (props.label && !props.id) { return new Error("Inputs must include an id when a label is specified for accessibility purposes."); } return null; }, /** Adds an optional label if there is a label provided * @param {Object} props Object of props * @returns {Error} Error or null */ isOptional: function verifyIsOptionalProp(props) { if (props.isOptional && !props.label) { return new Error("Must include a value for the label prop to use the isOptional prop"); } return null; }, /** Includes required asterisk label if true * @param {Object} props Object of props * @returns {Error} Error or null */ isRequired: function verifyIsRequiredProp(props) { if (props.isRequired && !props.label) { return new Error("Must include a value for the label prop to use the isRequired prop"); } return null; }, }; Input.displayName = "Input"; Input.defaultProps = { note: null, isRequired: false, hasSpellCheck: true, }; export default Input;