import { ChangeEvent, ReactNode, useEffect, useState } from 'react'; import { motion } from 'framer-motion'; import { isEmpty, isEqual } from 'lodash'; import styled from 'styled-components'; import { THEME_GLOBALS } from '../../themes/globals'; import { Noop } from '../../types/Util'; import { useHTMLDirection } from '../../util/i18n'; import { Flex } from '../basic/Flex'; import { SpacerMD } from '../basic/Text'; import { SessionIconButton } from '../icon'; const StyledInputContainer = styled(Flex)<{ error: boolean }>` position: relative; width: 100%; label { color: var(--text-primary-color); opacity: 0; transition: opacity var(--default-duration); text-align: center; &.filled { opacity: 1; } &.error { color: var(--danger-color); font-weight: 700; } } input::placeholder, textarea::placeholder { transition: opacity var(--default-duration) color var(--default-duration); ${props => props.error && `color: var(--danger-color); opacity: 1;`} } `; const StyledInput = styled(motion.input)<{ centerText?: boolean }>` border: 1px solid var(--input-border-color); border-radius: 13px; outline: 0; width: 100%; background: transparent; color: var(--input-text-color); font-family: var(--font-default); font-size: 12px; line-height: 14px; padding: var(--margins-lg); ${props => props.centerText && 'text-align: center;'} ::placeholder { color: var(--input-text-placeholder-color); ${props => props.centerText && 'text-align: center;'} } `; const StyledTextAreaContainer = styled(motion.div)<{ centerText?: boolean }>` border: 1px solid var(--input-border-color); border-radius: 13px; outline: 0; width: 100%; background: transparent; color: var(--input-text-color); font-family: var(--font-mono); font-size: var(--font-size-md); min-height: 100px; line-height: 18px; ${props => props.centerText && 'text-align: center;'} textarea { width: 100%; outline: 0; border: none; background: transparent; resize: none; overflow: hidden; overflow-wrap: break-word; user-select: all; display: inline-block; padding: var(--margins-lg); margin: var(--margins-xs) 0; ${props => props.centerText && 'text-align: center;'} :placeholder-shown { font-family: var(--font-default); height: 28px; margin: var(--margins-md) 0; padding: var(--margins-xl); } ::placeholder { color: var(--input-text-placeholder-color); ${props => props.centerText && 'text-align: center;'} } } `; const ErrorItem = (props: { id: string; error: string }) => { return ( {props.error} ); }; const ShowHideButton = (props: { forceShow: boolean; toggleForceShow: Noop; error: boolean }) => { const htmlDirection = useHTMLDirection(); const style = { position: 'absolute', top: '50%', transform: 'translateY(-50%)', left: htmlDirection === 'ltr' ? undefined : 'var(--margins-sm)', right: htmlDirection === 'ltr' ? 'var(--margins-sm)' : undefined, }; if (props.forceShow) { return ( ); } return ( ); }; const StyledCtaContainer = styled(motion.div)` width: 100%; `; type Props = { error?: string; type?: string; value?: string; placeholder: string; maxLength?: number; enableShowHide?: boolean; onValueChanged?: (value: string) => any; onEnterPressed?: (value: string) => any; autoFocus?: boolean; disabledOnBlur?: boolean; ref?: any; inputDataTestId?: string; id?: string; ctaButton?: ReactNode; /** Gives us a textarea with a monospace font. Mostly used for joining conversations, groups or communities */ isSpecial?: boolean; centerText?: boolean; }; export const SessionInput = (props: Props) => { const { placeholder, type = 'text', value, maxLength, enableShowHide, error, onValueChanged, onEnterPressed, autoFocus, disabledOnBlur, inputDataTestId, id = 'session-input-floating-label', ctaButton, isSpecial, centerText, } = props; const [inputValue, setInputValue] = useState(''); const [errorString, setErrorString] = useState(''); const [forceShow, setForceShow] = useState(false); const correctType = forceShow ? 'text' : type; const updateInputValue = (e: ChangeEvent) => { e.preventDefault(); const val = e.target.value; setInputValue(val); if (onValueChanged) { onValueChanged(val); } }; // TODO[epic=893] Type inputProps properly const inputProps: any = { id, type: correctType, placeholder, value, maxLength, autoFocus, 'data-testid': inputDataTestId, onChange: updateInputValue, style: { paddingInlineEnd: enableShowHide ? '48px' : undefined }, // just in case onChange isn't triggered onBlur: (event: ChangeEvent) => { if (!disabledOnBlur) { updateInputValue(event); } }, onKeyDown: (event: KeyboardEvent) => { if (event.key === 'Enter' && onEnterPressed) { if (isSpecial && event.shiftKey) { return; } event.preventDefault(); onEnterPressed(inputValue); setErrorString(''); } }, initial: { borderColor: errorString ? 'var(--input-border-color)' : undefined, }, animate: { borderColor: errorString ? 'var(--danger-color)' : undefined, }, transition: { duration: THEME_GLOBALS['--default-duration-seconds'] }, }; // if we have an error, we want to show it even if the input changes to a valid value useEffect(() => { if (error && !isEmpty(error) && !isEqual(error, errorString)) { setErrorString(error); } }, [error, errorString]); return ( {isSpecial ? (