mattermost/webapp/channels/src/components/quick_input/quick_input.tsx
Harrison Healey f3d73defcf
Some checks are pending
API / build (push) Waiting to run
Server CI / Compute Go Version (push) Waiting to run
Server CI / Check mocks (push) Blocked by required conditions
Server CI / Check go mod tidy (push) Blocked by required conditions
Server CI / check-style (push) Blocked by required conditions
Server CI / Check serialization methods for hot structs (push) Blocked by required conditions
Server CI / Vet API (push) Blocked by required conditions
Server CI / Check migration files (push) Blocked by required conditions
Server CI / Generate email templates (push) Blocked by required conditions
Server CI / Check store layers (push) Blocked by required conditions
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres with binary parameters (push) Blocked by required conditions
Server CI / Postgres (push) Blocked by required conditions
Server CI / Postgres (FIPS) (push) Blocked by required conditions
Server CI / Generate Test Coverage (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
MM-66937 Fix broken IME handling in Find Channels modal (#35264)
* MM-66937 Add E2E tests for bug

* MM-66937 Remove delayInputUpdate on that input to fix the bug

* Remove delayInputUpdate prop from QuickInput and SuggestionBox

* Run prettier

* Inline updateInputFromProps and remove eslint-disable that's no longer needed

* Fix snapshots
2026-02-16 11:00:59 +08:00

195 lines
5.6 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import classNames from 'classnames';
import type {ReactComponentLike} from 'prop-types';
import React, {useCallback, useEffect, useRef} from 'react';
import type {ReactNode} from 'react';
import {FormattedMessage} from 'react-intl';
import WithTooltip from 'components/with_tooltip';
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
*/
value?: string;
/**
* 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;
/**
* Callback to handle the key down of the input
*/
onKeyDown?: (event: React.KeyboardEvent) => void;
/**
* 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;
placeholder?: string;
autoFocus?: boolean;
type?: string;
id?: string;
onInput?: (e?: React.FormEvent<HTMLInputElement>) => void;
tabIndex?: number;
size?: 'md' | 'lg';
role?: string;
}
const defaultClearableTooltipText = (
<FormattedMessage
id={'input.clear'}
defaultMessage='Clear'
/>);
// 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
export const QuickInput = React.memo(({
value = '',
clearable = false,
autoFocus,
forwardedRef,
inputComponent,
clearClassName,
clearableWithoutValue,
clearableTooltipText,
onClear: onClearFromProps,
className,
size = 'md',
...restProps
}: Props) => {
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);
useEffect(() => {
if (autoFocus) {
requestAnimationFrame(() => {
inputRef.current?.focus();
});
}
/* eslint-disable-next-line react-hooks/exhaustive-deps --
* This 'useEffect' should only run once during mount.
**/
}, []);
useEffect(() => {
if (!inputRef.current || inputRef.current.value === value) {
return;
}
inputRef.current.value = value;
}, [value]);
const setInputRef = useCallback((input: HTMLInputElement) => {
if (forwardedRef) {
if (typeof forwardedRef === 'function') {
forwardedRef(input);
} else {
forwardedRef.current = input;
}
}
inputRef.current = input;
}, [forwardedRef]);
const onClear = useCallback((e: React.MouseEvent<HTMLButtonElement> | React.TouchEvent) => {
e.preventDefault();
e.stopPropagation();
if (onClearFromProps) {
onClearFromProps();
}
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
className: classNames(className, {
'form-control--lg': size === 'lg',
}),
},
);
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'
>
<i className='icon icon-close-circle'/>
</span>
</button>
</WithTooltip>
)}
</div>
);
});
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;