client/components/mma/paymentUpdate/FieldWrapper.tsx (137 lines of code) (raw):

import { css } from '@emotion/react'; import { error, focusHalo, FocusStyleManager, palette, textSansBold17, } from '@guardian/source/foundations'; import { InlineError } from '@guardian/source/react-components'; import type { StripeError } from '@stripe/stripe-js'; import * as React from 'react'; FocusStyleManager.onlyShowFocusOnTabs(); export interface FieldChangeEvent extends React.ChangeEvent<HTMLInputElement> { error: StripeError; } interface FieldWrapperProps { label: string; width: string; children: JSX.Element; cornerHint?: JSX.Element; onChange?: (event: FieldChangeEvent) => void; } interface FieldWrapperState { error: { code?: string; message?: string; type?: string; }; focus: boolean; } export class FieldWrapper extends React.Component< FieldWrapperProps, FieldWrapperState > { constructor(props: FieldWrapperProps) { super(props); this.state = { error: {}, focus: false, }; } public render(): React.ReactNode { const hydratedChildren = React.Children.map( this.props.children, (child) => { return React.cloneElement(child as React.ReactElement, { onChange: this.validateField(this.props.onChange), onFocus: this.toggleFocus, onBlur: this.toggleFocus, }); }, ); let borderCss: string; if (this.state.error?.message) { borderCss = '4px solid ' + error[400]; } else { borderCss = '2px solid ' + palette.neutral[60]; } return ( <div css={{ width: this.props.width, maxWidth: '100%', marginBottom: '10px', textAlign: 'left', ':not(:first-of-type)': { marginLeft: '20px', }, }} > <div css={ this.props.cornerHint ? css` display: flex; justify-content: space-between; align-items: end; ` : `` } > <div> <label css={css` ${textSansBold17}; color: ${palette.neutral[7]}; `} > {this.props.label} </label> {this.state.error?.message && ( <InlineError cssOverrides={css` margin-bottom: -5px; margin-top: 3px; `} > {this.state.error.message} </InlineError> )} </div> {this.props.cornerHint && this.props.cornerHint} </div> <div css={css` border: ${borderCss}; display: block; font-weight: 400; margin-top: 3px; line-height: 20px; padding: 10px; width: 100%; transition: all 0.2s ease-in-out; ${this.state.focus && focusHalo}; `} > {hydratedChildren} </div> </div> ); } private validateField = (otherOnChange?: (event: FieldChangeEvent) => void) => (field: FieldChangeEvent) => { if (otherOnChange) { otherOnChange(field); } this.setState({ error: field.error?.message ? field.error : {}, }); }; private toggleFocus = () => { this.setState({ focus: !this.state.focus, }); }; }