mirror of
https://github.com/lux-tel/session-desktop.git
synced 2026-10-30 20:33:24 +00:00
184 lines
5.0 KiB
TypeScript
184 lines
5.0 KiB
TypeScript
import { useEffect, useRef, type ReactNode } from 'react';
|
|
import { isEmpty } from 'lodash';
|
|
import styled from 'styled-components';
|
|
import { motion } from 'framer-motion';
|
|
import { type TrArgs } from '../../localization/localeTools';
|
|
import {
|
|
BorderWithErrorState,
|
|
type SessionInputTextSizes,
|
|
SimpleErrorItem,
|
|
StyledSessionInput,
|
|
type GenericSessionInputProps,
|
|
} from './SessionInput';
|
|
import { useUpdateInputValue } from './useUpdateInputValue';
|
|
import { SpacerMD } from '../basic/Text';
|
|
|
|
export const StyledTextAreaContainer = styled(motion.div)<{
|
|
$error: boolean;
|
|
$textSize: SessionInputTextSizes;
|
|
$monospaced?: boolean;
|
|
$padding?: string;
|
|
}>`
|
|
display: flex;
|
|
align-items: center;
|
|
position: relative;
|
|
line-height: 1;
|
|
height: 100%;
|
|
width: 100%;
|
|
padding: ${props => (props.$padding ? props.$padding : 'var(--margins-md)')};
|
|
|
|
background: transparent;
|
|
color: ${props => (props.$error ? 'var(--danger-color)' : 'var(--input-text-color)')};
|
|
outline: 0;
|
|
|
|
font-family: ${props => (props.$monospaced ? 'var(--font-mono)' : 'var(--font-default)')};
|
|
${props => `font-size: var(--font-size-${props.$textSize});`}
|
|
|
|
textarea {
|
|
display: flex;
|
|
height: 100%;
|
|
width: 100%;
|
|
outline: 0;
|
|
border: none;
|
|
background: transparent;
|
|
|
|
resize: none;
|
|
word-break: break-all;
|
|
user-select: all;
|
|
|
|
&:placeholder-shown {
|
|
line-height: 1;
|
|
font-family: ${props => (props.$monospaced ? 'var(--font-mono)' : 'var(--font-default)')};
|
|
${props => `font-size: var(--font-size-${props.$textSize});`}
|
|
}
|
|
|
|
&::placeholder {
|
|
color: var(--input-text-placeholder-color);
|
|
}
|
|
}
|
|
`;
|
|
|
|
/**
|
|
*
|
|
* Also, and just like SimpleSessionInput, error handling and value management is to be done by the parent component.
|
|
* Providing `error` will make the textarea red and the error string displayed below it.
|
|
* This component should only be used for TextArea that does not need remote validations, as the error
|
|
* state is live. For remote validations, use the SessionInput component.
|
|
*/
|
|
export const SimpleSessionTextarea = (
|
|
props: Pick<
|
|
GenericSessionInputProps,
|
|
| 'placeholder'
|
|
| 'value'
|
|
| 'ariaLabel'
|
|
| 'maxLength'
|
|
| 'autoFocus'
|
|
| 'inputDataTestId'
|
|
| 'textSize'
|
|
| 'padding'
|
|
| 'required'
|
|
| 'tabIndex'
|
|
> &
|
|
Required<Pick<GenericSessionInputProps, 'errorDataTestId'>> & {
|
|
onValueChanged: (str: string) => void;
|
|
providedError: string | TrArgs | undefined;
|
|
disabled?: boolean;
|
|
buttonEnd?: ReactNode;
|
|
} & ({ singleLine: false } | { singleLine: true; onEnterPressed: () => void })
|
|
) => {
|
|
const {
|
|
placeholder,
|
|
value,
|
|
ariaLabel,
|
|
maxLength,
|
|
providedError,
|
|
onValueChanged,
|
|
autoFocus,
|
|
inputDataTestId,
|
|
errorDataTestId,
|
|
textSize = 'sm',
|
|
disabled,
|
|
padding,
|
|
required,
|
|
tabIndex,
|
|
buttonEnd,
|
|
} = props;
|
|
const hasError = !isEmpty(providedError);
|
|
const hasValue = !isEmpty(value);
|
|
|
|
const ref = useRef<HTMLInputElement | null>(null);
|
|
|
|
const updateInputValue = useUpdateInputValue(onValueChanged, disabled);
|
|
|
|
const paddingInlineEnd = !!buttonEnd && hasValue ? '48px' : undefined;
|
|
|
|
const inputProps: any = {
|
|
type: 'text',
|
|
placeholder,
|
|
value,
|
|
textSize,
|
|
disabled,
|
|
maxLength,
|
|
padding,
|
|
autoFocus,
|
|
'data-testid': inputDataTestId,
|
|
required,
|
|
'aria-required': required,
|
|
tabIndex,
|
|
onChange: updateInputValue,
|
|
style: { paddingInlineEnd, lineHeight: 1.5 },
|
|
};
|
|
|
|
useEffect(() => {
|
|
const textarea = ref.current;
|
|
if (textarea) {
|
|
// don't ask me why, but we need to reset the height to auto before calculating it here
|
|
textarea.style.height = 'auto';
|
|
// we want 12 lines of text at most
|
|
textarea.style.height = `${Math.min(textarea.scrollHeight, 12 * parseFloat(getComputedStyle(textarea).lineHeight))}px`;
|
|
}
|
|
}, [ref, value]);
|
|
|
|
return (
|
|
<StyledSessionInput
|
|
$container={true}
|
|
$flexDirection="column"
|
|
$justifyContent="center"
|
|
$alignItems="center"
|
|
$error={hasError}
|
|
$textSize={textSize}
|
|
>
|
|
<BorderWithErrorState hasError={hasError}>
|
|
<StyledTextAreaContainer $error={hasError} $textSize={textSize} $padding={padding}>
|
|
<textarea
|
|
{...inputProps}
|
|
placeholder={disabled ? value : placeholder}
|
|
ref={ref}
|
|
aria-label={ariaLabel}
|
|
spellCheck={false} // maybe we should make this a prop, but it seems we never want spellcheck for those fields
|
|
onKeyDown={e => {
|
|
if (!props.singleLine) {
|
|
return;
|
|
}
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
props?.onEnterPressed();
|
|
}
|
|
}}
|
|
/>
|
|
</StyledTextAreaContainer>
|
|
|
|
{buttonEnd}
|
|
</BorderWithErrorState>
|
|
|
|
{hasError ? (
|
|
<>
|
|
<SpacerMD />
|
|
<SimpleErrorItem providedError={providedError} dataTestId={errorDataTestId} />
|
|
</>
|
|
) : null}
|
|
</StyledSessionInput>
|
|
);
|
|
};
|