
* Replace images with new SVG components, replace backgrounds * Add Denim and Onyx themes, fix associated themeing issues * Add back elevation variables --------- Co-authored-by: Mattermost Build <build@mattermost.com>
194 lines
6.1 KiB
TypeScript
194 lines
6.1 KiB
TypeScript
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import classNames from 'classnames';
|
|
import React, {useState, useEffect} from 'react';
|
|
import {useIntl} from 'react-intl';
|
|
|
|
import 'renderer/css/components/Input.scss';
|
|
|
|
export enum STATUS {
|
|
NONE = 'none',
|
|
SUCCESS = 'success',
|
|
INFO = 'info',
|
|
WARNING = 'warning',
|
|
ERROR = 'error',
|
|
}
|
|
|
|
export enum SIZE {
|
|
MEDIUM = 'medium',
|
|
LARGE = 'large',
|
|
}
|
|
|
|
export type CustomMessageInputType = {
|
|
type: STATUS;
|
|
value: string;
|
|
} | null;
|
|
|
|
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
required?: boolean;
|
|
hasError?: boolean;
|
|
addon?: React.ReactElement;
|
|
textPrefix?: string;
|
|
inputPrefix?: JSX.Element;
|
|
inputSuffix?: JSX.Element;
|
|
label?: string;
|
|
containerClassName?: string;
|
|
wrapperClassName?: string;
|
|
inputClassName?: string;
|
|
limit?: number;
|
|
useLegend?: boolean;
|
|
customMessage?: CustomMessageInputType;
|
|
inputSize?: SIZE;
|
|
}
|
|
|
|
const Input = React.forwardRef((
|
|
{
|
|
name,
|
|
value,
|
|
label,
|
|
placeholder,
|
|
useLegend = true,
|
|
className,
|
|
hasError,
|
|
required,
|
|
addon,
|
|
textPrefix,
|
|
inputPrefix,
|
|
inputSuffix,
|
|
containerClassName,
|
|
wrapperClassName,
|
|
inputClassName,
|
|
limit,
|
|
customMessage,
|
|
maxLength,
|
|
inputSize = SIZE.MEDIUM,
|
|
disabled,
|
|
onFocus,
|
|
onBlur,
|
|
onChange,
|
|
...otherProps
|
|
}: InputProps,
|
|
ref?: React.Ref<HTMLInputElement>,
|
|
) => {
|
|
const {formatMessage} = useIntl();
|
|
|
|
const [focused, setFocused] = useState(false);
|
|
const [customInputLabel, setCustomInputLabel] = useState<CustomMessageInputType>(null);
|
|
|
|
useEffect(() => {
|
|
if (customMessage !== undefined && customMessage !== null && customMessage.value !== '') {
|
|
setCustomInputLabel(customMessage);
|
|
}
|
|
}, [customMessage]);
|
|
|
|
const handleOnFocus = (event: React.FocusEvent<HTMLInputElement>) => {
|
|
setFocused(true);
|
|
|
|
if (onFocus) {
|
|
onFocus(event);
|
|
}
|
|
};
|
|
|
|
const handleOnBlur = (event: React.FocusEvent<HTMLInputElement>) => {
|
|
setFocused(false);
|
|
validateInput();
|
|
|
|
if (onBlur) {
|
|
onBlur(event);
|
|
}
|
|
};
|
|
|
|
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
setCustomInputLabel(null);
|
|
|
|
if (onChange) {
|
|
onChange(event);
|
|
}
|
|
};
|
|
|
|
const validateInput = () => {
|
|
if (!required || (value !== null && value !== '')) {
|
|
return;
|
|
}
|
|
const validationErrorMsg = formatMessage({id: 'renderer.components.input.required', defaultMessage: 'This field is required'});
|
|
setCustomInputLabel({type: STATUS.ERROR, value: validationErrorMsg});
|
|
};
|
|
|
|
const showLegend = Boolean(focused || value);
|
|
const error = customInputLabel?.type === 'error';
|
|
const limitExceeded = limit && value && !Array.isArray(value) ? value.toString().length - limit : 0;
|
|
|
|
return (
|
|
<div
|
|
className={classNames(
|
|
'Input_container',
|
|
containerClassName,
|
|
{
|
|
disabled,
|
|
},
|
|
)}
|
|
>
|
|
<fieldset
|
|
className={classNames('Input_fieldset', className, {
|
|
Input_fieldset___error: error || hasError || limitExceeded > 0,
|
|
Input_fieldset___legend: showLegend,
|
|
})}
|
|
>
|
|
{useLegend && (
|
|
<legend className={classNames('Input_legend', {Input_legend___focus: showLegend})}>
|
|
{showLegend ? label || placeholder : null}
|
|
</legend>
|
|
)}
|
|
<div className={classNames('Input_wrapper', wrapperClassName)}>
|
|
{inputPrefix}
|
|
{textPrefix && <span>{textPrefix}</span>}
|
|
<input
|
|
ref={ref}
|
|
id={`input_${name || ''}`}
|
|
className={classNames('Input', inputSize, inputClassName, {Input__focus: showLegend})}
|
|
value={value}
|
|
placeholder={focused ? (label && placeholder) || label : label || placeholder}
|
|
name={name}
|
|
disabled={disabled}
|
|
{...otherProps}
|
|
maxLength={limit ? undefined : maxLength}
|
|
onFocus={handleOnFocus}
|
|
onBlur={handleOnBlur}
|
|
onChange={handleOnChange}
|
|
/>
|
|
{limitExceeded > 0 && (
|
|
<span className='Input_limit-exceeded'>
|
|
{'-'}{limitExceeded}
|
|
</span>
|
|
)}
|
|
{inputSuffix}
|
|
</div>
|
|
{addon}
|
|
</fieldset>
|
|
{customInputLabel && (
|
|
<div
|
|
id={`customMessage_${name || ''}`}
|
|
className={`Input___customMessage Input___${customInputLabel.type}`}
|
|
>
|
|
{customInputLabel.type !== STATUS.INFO && (
|
|
<i
|
|
className={classNames(`icon ${customInputLabel.type}`, {
|
|
'icon-alert-outline': customInputLabel.type === STATUS.WARNING,
|
|
'icon-alert-circle-outline': customInputLabel.type === STATUS.ERROR,
|
|
'icon-check': customInputLabel.type === STATUS.SUCCESS,
|
|
|
|
// No icon wanted for info in desktop. Kept for further reference with Input component in webapp
|
|
// 'icon-information-outline': customInputLabel.type === STATUS.INFO,
|
|
})}
|
|
/>
|
|
)}
|
|
<span>{customInputLabel.value}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
});
|
|
|
|
export default Input;
|