2023-03-22 17:22:27 -04:00
|
|
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
|
|
|
// See LICENSE.txt for license information.
|
|
|
|
|
|
|
|
|
|
import classNames from 'classnames';
|
2023-09-06 06:51:55 -04:00
|
|
|
import type {ReactComponentLike} from 'prop-types';
|
2025-06-30 10:05:25 -04:00
|
|
|
import React, {useCallback, useEffect, useRef} from 'react';
|
2023-09-06 06:51:55 -04:00
|
|
|
import type {ReactNode} from 'react';
|
|
|
|
|
import {FormattedMessage} from 'react-intl';
|
2023-03-22 17:22:27 -04:00
|
|
|
|
2024-07-28 14:50:46 -04:00
|
|
|
import WithTooltip from 'components/with_tooltip';
|
2023-03-22 17:22:27 -04:00
|
|
|
|
|
|
|
|
export type Props = {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* An optional React component that will be used instead of an HTML input when rendering
|
|
|
|
|
*/
|
|
|
|
|
inputComponent?: ReactComponentLike;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The string value displayed in this input
|
|
|
|
|
*/
|
2025-06-30 10:05:25 -04:00
|
|
|
value?: string;
|
2023-03-22 17:22:27 -04:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* When true, and an onClear callback is defined, show an X on the input field that clears
|
|
|
|
|
* the input when clicked.
|
|
|
|
|
*/
|
|
|
|
|
clearable?: boolean;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The optional tooltip text to display on the X shown when clearable. Pass a components
|
|
|
|
|
* such as FormattedMessage to localize.
|
|
|
|
|
*/
|
|
|
|
|
clearableTooltipText?: string | ReactNode;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Callback to clear the input value, and used in tandem with the clearable prop above.
|
|
|
|
|
*/
|
|
|
|
|
onClear?: () => void;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ClassName for the clear button container
|
|
|
|
|
*/
|
|
|
|
|
clearClassName?: string;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Callback to handle the change event of the input
|
|
|
|
|
*/
|
|
|
|
|
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Callback to handle the key up of the input
|
|
|
|
|
*/
|
|
|
|
|
onKeyUp?: (event: React.KeyboardEvent) => void;
|
|
|
|
|
|
2024-10-14 06:56:35 -04:00
|
|
|
/**
|
|
|
|
|
* Callback to handle the key down of the input
|
|
|
|
|
*/
|
|
|
|
|
onKeyDown?: (event: React.KeyboardEvent) => void;
|
|
|
|
|
|
2023-03-22 17:22:27 -04:00
|
|
|
/**
|
|
|
|
|
* When true, and an onClear callback is defined, show an X on the input field even if
|
|
|
|
|
* the input is empty.
|
|
|
|
|
*/
|
|
|
|
|
clearableWithoutValue?: boolean;
|
|
|
|
|
|
|
|
|
|
forwardedRef?: ((instance: HTMLInputElement | HTMLTextAreaElement | null) => void) | React.MutableRefObject<HTMLInputElement | HTMLTextAreaElement | null> | null;
|
|
|
|
|
|
|
|
|
|
maxLength?: number;
|
|
|
|
|
className?: string;
|
2024-01-16 04:10:24 -05:00
|
|
|
placeholder?: string;
|
2023-03-22 17:22:27 -04:00
|
|
|
autoFocus?: boolean;
|
|
|
|
|
type?: string;
|
|
|
|
|
id?: string;
|
|
|
|
|
onInput?: (e?: React.FormEvent<HTMLInputElement>) => void;
|
2024-10-14 06:56:35 -04:00
|
|
|
tabIndex?: number;
|
2025-07-24 05:31:56 -04:00
|
|
|
size?: 'md' | 'lg';
|
2025-06-17 17:47:30 -04:00
|
|
|
role?: string;
|
2023-03-22 17:22:27 -04:00
|
|
|
}
|
|
|
|
|
|
2025-06-30 10:05:25 -04:00
|
|
|
const defaultClearableTooltipText = (
|
|
|
|
|
<FormattedMessage
|
|
|
|
|
id={'input.clear'}
|
|
|
|
|
defaultMessage='Clear'
|
|
|
|
|
/>);
|
|
|
|
|
|
2023-03-22 17:22:27 -04:00
|
|
|
// A component that can be used to make controlled inputs that function properly in certain
|
|
|
|
|
// environments (ie. IE11) where typing quickly would sometimes miss inputs
|
2025-06-30 10:05:25 -04:00
|
|
|
export const QuickInput = React.memo(({
|
|
|
|
|
value = '',
|
|
|
|
|
clearable = false,
|
|
|
|
|
autoFocus,
|
|
|
|
|
forwardedRef,
|
|
|
|
|
inputComponent,
|
|
|
|
|
clearClassName,
|
|
|
|
|
clearableWithoutValue,
|
|
|
|
|
clearableTooltipText,
|
|
|
|
|
onClear: onClearFromProps,
|
2025-07-24 05:31:56 -04:00
|
|
|
className,
|
|
|
|
|
size = 'md',
|
2025-06-30 10:05:25 -04:00
|
|
|
...restProps
|
|
|
|
|
}: Props) => {
|
|
|
|
|
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (autoFocus) {
|
2023-03-22 17:22:27 -04:00
|
|
|
requestAnimationFrame(() => {
|
2025-06-30 10:05:25 -04:00
|
|
|
inputRef.current?.focus();
|
2023-03-22 17:22:27 -04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 10:05:25 -04:00
|
|
|
/* eslint-disable-next-line react-hooks/exhaustive-deps --
|
|
|
|
|
* This 'useEffect' should only run once during mount.
|
|
|
|
|
**/
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-02-15 22:00:59 -05:00
|
|
|
if (!inputRef.current || inputRef.current.value === value) {
|
|
|
|
|
return;
|
2023-03-22 17:22:27 -04:00
|
|
|
}
|
|
|
|
|
|
2026-02-15 22:00:59 -05:00
|
|
|
inputRef.current.value = value;
|
2025-06-30 10:05:25 -04:00
|
|
|
}, [value]);
|
2023-03-22 17:22:27 -04:00
|
|
|
|
2025-06-30 10:05:25 -04:00
|
|
|
const setInputRef = useCallback((input: HTMLInputElement) => {
|
|
|
|
|
if (forwardedRef) {
|
|
|
|
|
if (typeof forwardedRef === 'function') {
|
|
|
|
|
forwardedRef(input);
|
2023-03-22 17:22:27 -04:00
|
|
|
} else {
|
2025-06-30 10:05:25 -04:00
|
|
|
forwardedRef.current = input;
|
2023-03-22 17:22:27 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 10:05:25 -04:00
|
|
|
inputRef.current = input;
|
|
|
|
|
}, [forwardedRef]);
|
2023-03-22 17:22:27 -04:00
|
|
|
|
2025-06-30 10:05:25 -04:00
|
|
|
const onClear = useCallback((e: React.MouseEvent<HTMLButtonElement> | React.TouchEvent) => {
|
2023-03-22 17:22:27 -04:00
|
|
|
e.preventDefault();
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
2025-06-30 10:05:25 -04:00
|
|
|
if (onClearFromProps) {
|
|
|
|
|
onClearFromProps();
|
2023-03-22 17:22:27 -04:00
|
|
|
}
|
|
|
|
|
|
2025-06-30 10:05:25 -04:00
|
|
|
inputRef.current?.focus();
|
|
|
|
|
}, [onClearFromProps]);
|
|
|
|
|
|
|
|
|
|
const showClearButton = onClearFromProps && (clearableWithoutValue || (clearable && value));
|
|
|
|
|
|
|
|
|
|
const inputElement = React.createElement(
|
|
|
|
|
inputComponent || 'input',
|
|
|
|
|
{
|
|
|
|
|
...restProps,
|
|
|
|
|
ref: setInputRef,
|
|
|
|
|
defaultValue: value, // Only set the defaultValue since the real one will be updated using the 'useEffect' above
|
2025-07-24 05:31:56 -04:00
|
|
|
className: classNames(className, {
|
|
|
|
|
'form-control--lg': size === 'lg',
|
|
|
|
|
}),
|
2025-06-30 10:05:25 -04:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className='input-wrapper'>
|
|
|
|
|
{inputElement}
|
|
|
|
|
{showClearButton && (
|
|
|
|
|
<WithTooltip title={clearableTooltipText || defaultClearableTooltipText}>
|
|
|
|
|
<button
|
|
|
|
|
data-testid='input-clear'
|
|
|
|
|
className={classNames(clearClassName, 'input-clear visible')}
|
|
|
|
|
onClick={onClear}
|
|
|
|
|
>
|
|
|
|
|
<span
|
|
|
|
|
className='input-clear-x'
|
|
|
|
|
aria-hidden='true'
|
2025-01-05 14:20:44 -05:00
|
|
|
>
|
2025-06-30 10:05:25 -04:00
|
|
|
<i className='icon icon-close-circle'/>
|
|
|
|
|
</span>
|
|
|
|
|
</button>
|
|
|
|
|
</WithTooltip>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
});
|
2023-03-22 17:22:27 -04:00
|
|
|
|
|
|
|
|
type ForwardedProps = Omit<React.ComponentPropsWithoutRef<typeof QuickInput>, 'forwardedRef'>;
|
|
|
|
|
|
|
|
|
|
const forwarded = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, ForwardedProps>((props, ref) => (
|
|
|
|
|
<QuickInput
|
|
|
|
|
forwardedRef={ref}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
));
|
|
|
|
|
forwarded.displayName = 'QuickInput';
|
|
|
|
|
|
|
|
|
|
export default forwarded;
|