client/components/shared/Input.tsx (149 lines of code) (raw):

import type { SerializedStyles } from '@emotion/react'; import { css } from '@emotion/react'; import { focusHalo, palette, textSans17 } from '@guardian/source/foundations'; import { useEffect, useRef } from 'react'; import type * as React from 'react'; import { ErrorIcon } from '../mma/shared/assets/ErrorIcon'; type SetStateFunc = (value: string) => void; interface InputProps { type?: string; step?: string; min?: string; label: string; secondaryLabel?: string; width: number; value: string | number; optional?: boolean; name?: string; id?: string; changeSetState?: SetStateFunc; onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void; setFocus?: boolean; inErrorState?: boolean; errorMessage?: string; prefixValue?: string; additionalCss?: SerializedStyles; } export const Input = (props: InputProps) => { const inputEl = useRef<HTMLInputElement>(null); useEffect(() => { if (props.setFocus) { inputEl.current?.focus(); } }, [props.setFocus]); return ( <label css={css` display: block; color: ${palette.neutral['7']}; ${textSans17}; font-weight: bold; ${props.additionalCss} `} data-qm-masking="blocklist" > {props.label} {props.optional && ( <span css={css` font-style: italic; font-weight: normal; color: ${palette.neutral['46']}; `} > {' '} optional </span> )} {props.secondaryLabel && ( <span css={css` display: block; font-weight: normal; color: ${palette.neutral['46']}; max-width: ${props.width}ch; `} > {props.secondaryLabel} </span> )} {props.inErrorState && ( <span css={css` display: block; font-weight: normal; color: ${palette.error[400]}; `} > <ErrorIcon additionalCss={css` margin-right: 4px; `} /> {props.errorMessage} </span> )} <div> {props.prefixValue && ( <span css={css` ${textSans17}; position: relative; z-index: 2; left: 1ch; `} > {props.prefixValue} </span> )} <input type={props.type || 'text'} name={props.name} id={props.id} step={props.step} min={props.min} value={props.value} ref={inputEl} onChange={(e: React.ChangeEvent<HTMLInputElement>): void => props.changeSetState && props.changeSetState( `${ props.type === 'number' ? e.target.valueAsNumber : e.target.value }`, ) } onFocus={(e: React.FocusEvent<HTMLInputElement>): void => props.onFocus && props.onFocus(e) } css={css` width: ${props.prefixValue ? `calc(100% - 4px)` : `100%`}; max-width: ${props.width}ch; height: 44px; ${textSans17}; color: ${palette.neutral['7']}; margin-top: 4px; padding: 0 8px; background-color: ${palette.neutral['100']}; border: ${props.inErrorState ? 4 : 2}px solid ${props.inErrorState ? palette.error[400] : palette.neutral['60']}; ${props.prefixValue && ` margin-left: calc(-${props.prefixValue.length}ch + 4px); box-sizing: border-box; padding-left: calc(${props.prefixValue.length}ch + 10px); `} &:focus { ${focusHalo}; } `} /> </div> </label> ); };