signal-desktop/ts/components/AutoSizeInput.tsx
2024-01-18 20:53:24 +01:00

99 lines
2.2 KiB
TypeScript

// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useCallback, useState, useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';
import classNames from 'classnames';
import { getClassNamesFor } from '../util/getClassNamesFor';
export type PropsType = Readonly<{
disableSpellcheck?: boolean;
disabled?: boolean;
moduleClassName?: string;
onChange: (newValue: string) => void;
onEnter?: () => void;
placeholder: string;
value?: string;
maxLength?: number;
}>;
export function AutoSizeInput({
disableSpellcheck,
disabled,
moduleClassName,
onChange,
onEnter,
placeholder,
value = '',
maxLength,
}: PropsType): JSX.Element {
const [root, setRoot] = useState<HTMLElement | null>(null);
const hiddenRef = useRef<HTMLSpanElement | null>(null);
const [width, setWidth] = useState<undefined | number>(undefined);
const getClassName = getClassNamesFor('AutoSizeInput', moduleClassName);
const handleChange = useCallback(
e => {
onChange(e.target.value);
},
[onChange]
);
const handleKeyDown = useCallback(
event => {
if (onEnter && event.key === 'Enter') {
onEnter();
}
},
[onEnter]
);
useEffect(() => {
const elem = document.createElement('div');
document.body.appendChild(elem);
setRoot(elem);
return () => {
document.body.removeChild(elem);
};
}, []);
useEffect(() => {
setWidth(hiddenRef.current?.clientWidth || undefined);
}, [value, root]);
return (
<div className={getClassName('__container')}>
<input
type="text"
className={getClassName('__input')}
dir="auto"
maxLength={maxLength}
value={value}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder={placeholder}
disabled={disabled}
spellCheck={!disableSpellcheck}
style={{ width }}
/>
{root &&
createPortal(
<span
ref={hiddenRef}
className={classNames(
getClassName('__input'),
getClassName('__input--sizer')
)}
>
{value || placeholder}
</span>,
root
)}
</div>
);
}