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 ? (
<>
{!isEmptyString(iconLeft) && (
)}
{!isEmptyString(iconRight) && (
)}
{canToggleVisibility && (
setHide(!hide)}
/>
)}
{!hideStdErr && }
>
) : htmlType !== 'password' ? (
) : (
<>>
)}
)
})