import React, { ChangeEvent, forwardRef, ForwardRefExoticComponent, MouseEvent, PropsWithoutRef, RefAttributes, useState, } from 'react' import styled, { css, SimpleInterpolation } from 'styled-components' import { baseDisabledStyles, baseErrorBorderStyles, borderRadius, buttonTransition, Colors, flexFlow, FontSizes, getColor, typographyFont, } from '@monorail/helpers/exports' import { isEmptyString } from '@monorail/sharedHelpers/typeGuards' import { Icon, IconProps } from '@monorail/visualComponents/icon/Icon' import { IconType } from '@monorail/visualComponents/icon/IconType' import { DisplayType } from '@monorail/visualComponents/inputs/inputTypes' import { Label } from '@monorail/visualComponents/inputs/Label' import { ErrorProps, StdErr } from '@monorail/visualComponents/inputs/StdErr' import { ViewInput } from '@monorail/visualComponents/inputs/ViewInput' /* * Styles */ const Container = styled.label< ContainerProps & { display?: DisplayType } & { hideStdErr?: boolean } >( ({ cssOverrides, display, hideStdErr }) => css` ${flexFlow()}; float: none; width: 256px; position: relative; /* position: relative; so that the icons can be absolutely positioned. */ ${display !== DisplayType.Edit && !hideStdErr && `margin-bottom: 24px;`} ${cssOverrides} `, ) export const IconsAndInputContainer = styled.div` ${flexFlow('column')}; flex: 1; position: relative; ` const baseIconStyles = css` position: absolute; top: 50%; transform: translateY(-50%); ` const StyledLeftIcon = styled( ({ canToggleVisibility, err, msg, ...iconProps }: StyledIconProps) => ( ), )` ${baseIconStyles}; left: 8px; ` const StyledRightIcon = styled( ({ canToggleVisibility, err, msg, ...iconProps }: StyledIconProps) => ( ), )( ({ canToggleVisibility }: StyledIconProps) => css` ${baseIconStyles}; right: ${canToggleVisibility ? '32px' : '8px'}; `, ) const StyledVisibilityIcon = styled(Icon)` ${baseIconStyles}; cursor: pointer; right: 8px; ` export const StyledInput = styled.input( ({ chromeless, iconLeft, iconRight, disabled, canToggleVisibility, err, }) => css` ${disabled && baseDisabledStyles}; ${typographyFont(400, FontSizes.Title5)}; ${borderRadius()}; border-color: ${getColor(Colors.Black, 0.12)}; border-style: solid; border-width: 1px; box-sizing: border-box; color: ${getColor(Colors.Black89a)}; height: 24px; min-height: 24px; /* IE11 needs min-height for reasons Izak doesn't understand. */ flex: 1; outline: none; padding: 4px ${iconRight ? canToggleVisibility ? 56 : 30 : canToggleVisibility ? 30 : 6}px 4px ${iconLeft ? 30 : 6}px; width: 100%; ${buttonTransition}; &[htmlType='number'] { &::-webkit-inner-spin-button, &::-webkit-outer-spin-button { opacity: 1; } } &:hover { border-color: ${getColor(Colors.Black, 0.3)}; } &:focus, &:active { border-color: ${getColor(Colors.BrandLightBlue)}; } ::placeholder { color: ${getColor(Colors.Black54a)}; font-style: italic; } ${chromeless && css` border-color: transparent; `}; /* Remove :-moz-ui-invalid styles so that invalid form states look similar across browsers https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-ui-invalid */ :-moz-ui-invalid { box-shadow: none; } ${err && baseErrorBorderStyles}; ${disabled && baseDisabledStyles}; `, ) /* * Types */ export type InputHTMLType = | 'button' | 'checkbox' | 'color' // | 'date' // @deprecated @use FormField.Date // | 'datetime' // @deprecated // | 'datetime-local' // @deprecated @use FormField.Date | 'email' | 'file' | 'hidden' | 'image' | 'month' | 'number' | 'password' | 'radio' | 'range' | 'reset' | 'search' | 'submit' | 'tel' | 'text' | 'time' | 'url' | 'week' type ContainerProps = { cssOverrides?: SimpleInterpolation className?: string } type VisibilityProps = { canToggleVisibility?: boolean } type ExtraProps = { chromeless?: boolean min?: number max?: number maxLength?: number } type BasicProps = { iconLeft?: IconType enableLastPass?: boolean iconRight?: IconType label?: string onChange?: ( event: ChangeEvent, ) => void onClick?: (event: MouseEvent) => void onBlur?: (e: React.FocusEvent) => void onFocus?: ( e: React.FocusEvent, ) => void onKeyDown?: (e: React.KeyboardEvent) => void placeholder?: string value?: string | number // TODO - split into number component disabled?: boolean readOnly?: boolean required?: boolean htmlValidation?: boolean htmlType?: InputHTMLType autoFocus?: boolean pattern?: string name?: string hideStdErr?: boolean display?: DisplayType labelDetails?: string | React.ReactElement } type StyledIconProps = VisibilityProps & IconProps & ErrorProps export type TextFieldProps = ContainerProps & VisibilityProps & BasicProps & ExtraProps & ErrorProps /* * Component */ export const TextField: ForwardRefExoticComponent & RefAttributes> = forwardRef< HTMLInputElement, TextFieldProps >((props, ref) => { const [hide, setHide] = useState(true) const { autoFocus = false, canToggleVisibility = false, chromeless = false, cssOverrides = '', enableLastPass = false, htmlValidation = true, iconLeft = '', iconRight = '', label = '', onChange = () => {}, onBlur, placeholder = '', value, disabled = false, display = DisplayType.Edit, readOnly = false, required = false, htmlType = canToggleVisibility && hide ? 'password' : 'text', min = 0, max = 9999, maxLength = 1000, className = '', err = false, msg = '', hideStdErr = false, labelDetails, ...otherProps } = props const lpIgnore = !enableLastPass return ( {display === DisplayType.Edit ? ( <> ) })