ref(ui-components) Use new input and button in chat (#12000)
This commit is contained in:
parent
ea4be6cb30
commit
0435c3cc64
|
@ -119,63 +119,18 @@
|
|||
|
||||
.chat-input-container {
|
||||
padding: 0 16px 16px;
|
||||
|
||||
&.populated {
|
||||
#chat-input {
|
||||
.send-button {
|
||||
background: #1B67EC;
|
||||
cursor: pointer;
|
||||
margin-left: 0.3rem;
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background: #3D82FB;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #0852D4;
|
||||
}
|
||||
|
||||
path {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#chat-input {
|
||||
border: 1px solid $chatInputSeparatorColor;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding: 4px;
|
||||
border-radius: 3px;
|
||||
|
||||
&:focus-within {
|
||||
border: 1px solid #619CF4;
|
||||
}
|
||||
|
||||
* {
|
||||
background-color: transparent;
|
||||
}
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.send-button-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.send-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 3px;
|
||||
|
||||
path {
|
||||
fill: $chatInputSeparatorColor;
|
||||
}
|
||||
.chat-input {
|
||||
flex: 1;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.smiley-button {
|
||||
|
@ -395,7 +350,9 @@
|
|||
|
||||
.smiley-input {
|
||||
display: flex;
|
||||
position: relative;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.smileys-panel {
|
||||
|
@ -403,7 +360,7 @@
|
|||
box-sizing: border-box;
|
||||
background-color: rgba(0, 0, 0, .6) !important;
|
||||
height: auto;
|
||||
display: none;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
width: calc(#{$sidebarWidth} - 32px);
|
||||
|
@ -418,11 +375,6 @@
|
|||
*/
|
||||
transition: max-height 0.3s;
|
||||
|
||||
&.show-smileys {
|
||||
display: flex;
|
||||
max-height: 500%;
|
||||
}
|
||||
|
||||
#smileysContainer {
|
||||
background-color: $chatBackgroundColor;
|
||||
border-top: 1px solid $chatInputSeparatorColor;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { makeStyles } from '@material-ui/core';
|
||||
import clsx from 'clsx';
|
||||
import React, { useCallback } from 'react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
|
||||
import { isMobileBrowser } from '../../../environment/utils';
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
|
@ -14,10 +15,14 @@ interface IInputProps extends InputProps {
|
|||
autoFocus?: boolean;
|
||||
bottomLabel?: string;
|
||||
className?: string;
|
||||
iconClick?: () => void;
|
||||
id?: string;
|
||||
maxLength?: number;
|
||||
maxRows?: number;
|
||||
minRows?: number;
|
||||
name?: string;
|
||||
onKeyPress?: (e: React.KeyboardEvent) => void;
|
||||
textarea?: boolean;
|
||||
type?: 'text' | 'email' | 'number' | 'password';
|
||||
}
|
||||
|
||||
|
@ -73,6 +78,10 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
},
|
||||
|
||||
'&.icon-input': {
|
||||
paddingLeft: '46px'
|
||||
},
|
||||
|
||||
'&.error': {
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.textError}`
|
||||
}
|
||||
|
@ -80,12 +89,13 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
|
||||
icon: {
|
||||
position: 'absolute',
|
||||
top: '10px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
left: '16px'
|
||||
},
|
||||
|
||||
iconInput: {
|
||||
paddingLeft: '46px'
|
||||
iconClickable: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
|
||||
clearableInput: {
|
||||
|
@ -118,7 +128,7 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
const Input = ({
|
||||
const Input = React.forwardRef<any, IInputProps>(({
|
||||
accessibilityLabel,
|
||||
autoFocus,
|
||||
bottomLabel,
|
||||
|
@ -127,20 +137,24 @@ const Input = ({
|
|||
disabled,
|
||||
error,
|
||||
icon,
|
||||
iconClick,
|
||||
id,
|
||||
label,
|
||||
maxLength,
|
||||
maxRows,
|
||||
minRows,
|
||||
name,
|
||||
onChange,
|
||||
onKeyPress,
|
||||
placeholder,
|
||||
textarea = false,
|
||||
type = 'text',
|
||||
value
|
||||
}: IInputProps) => {
|
||||
}: IInputProps, ref) => {
|
||||
const styles = useStyles();
|
||||
const isMobile = isMobileBrowser();
|
||||
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement|HTMLTextAreaElement>) =>
|
||||
onChange(e.target.value), []);
|
||||
|
||||
const clearInput = useCallback(() => onChange(''), []);
|
||||
|
@ -149,23 +163,44 @@ const Input = ({
|
|||
{label && <span className = { clsx(styles.label, isMobile && 'is-mobile') }>{label}</span>}
|
||||
<div className = { styles.fieldContainer }>
|
||||
{icon && <Icon
|
||||
className = { styles.icon }
|
||||
{ ...(iconClick ? { tabIndex: 0 } : {}) }
|
||||
className = { clsx(styles.icon, iconClick && styles.iconClickable) }
|
||||
onClick = { iconClick }
|
||||
size = { 20 }
|
||||
src = { icon } />}
|
||||
<input
|
||||
aria-label = { accessibilityLabel }
|
||||
autoFocus = { autoFocus }
|
||||
className = { clsx(styles.input, isMobile && 'is-mobile',
|
||||
error && 'error', clearable && styles.clearableInput, icon && styles.iconInput) }
|
||||
disabled = { disabled }
|
||||
{ ...(id ? { id } : {}) }
|
||||
maxLength = { maxLength }
|
||||
name = { name }
|
||||
onChange = { handleChange }
|
||||
onKeyPress = { onKeyPress }
|
||||
placeholder = { placeholder }
|
||||
type = { type }
|
||||
value = { value } />
|
||||
{textarea ? (
|
||||
<TextareaAutosize
|
||||
aria-label = { accessibilityLabel }
|
||||
autoFocus = { autoFocus }
|
||||
className = { clsx(styles.input, isMobile && 'is-mobile',
|
||||
error && 'error', clearable && styles.clearableInput, icon && 'icon-input') }
|
||||
disabled = { disabled }
|
||||
{ ...(id ? { id } : {}) }
|
||||
maxRows = { maxRows }
|
||||
minRows = { minRows }
|
||||
name = { name }
|
||||
onChange = { handleChange }
|
||||
onKeyPress = { onKeyPress }
|
||||
placeholder = { placeholder }
|
||||
ref = { ref }
|
||||
value = { value } />
|
||||
) : (
|
||||
<input
|
||||
aria-label = { accessibilityLabel }
|
||||
autoFocus = { autoFocus }
|
||||
className = { clsx(styles.input, isMobile && 'is-mobile',
|
||||
error && 'error', clearable && styles.clearableInput, icon && 'icon-input') }
|
||||
disabled = { disabled }
|
||||
{ ...(id ? { id } : {}) }
|
||||
maxLength = { maxLength }
|
||||
name = { name }
|
||||
onChange = { handleChange }
|
||||
onKeyPress = { onKeyPress }
|
||||
placeholder = { placeholder }
|
||||
ref = { ref }
|
||||
type = { type }
|
||||
value = { value } />
|
||||
)}
|
||||
{clearable && !disabled && value !== '' && <button className = { styles.clearButton }>
|
||||
<Icon
|
||||
onClick = { clearInput }
|
||||
|
@ -179,6 +214,6 @@ const Input = ({
|
|||
</span>
|
||||
)}
|
||||
</div>);
|
||||
};
|
||||
});
|
||||
|
||||
export default Input;
|
||||
|
|
|
@ -1,21 +1,30 @@
|
|||
// @flow
|
||||
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React, { Component } from 'react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { IState } from '../../../app/types';
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconPlane, IconSmile } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconPlane, IconSmile } from '../../../base/icons/svg/index';
|
||||
import { connect } from '../../../base/redux/functions';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import Input from '../../../base/ui/components/web/Input';
|
||||
// @ts-ignore
|
||||
import { areSmileysDisabled } from '../../functions';
|
||||
|
||||
// @ts-ignore
|
||||
import SmileysPanel from './SmileysPanel';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ChatInput}.
|
||||
*/
|
||||
type Props = {
|
||||
interface Props extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Whether chat emoticons are disabled.
|
||||
*/
|
||||
_areSmileysDisabled: boolean,
|
||||
|
||||
/**
|
||||
* Invoked to send chat messages.
|
||||
|
@ -26,24 +35,14 @@ type Props = {
|
|||
* Optional callback to invoke when the chat textarea has auto-resized to
|
||||
* fit overflowing text.
|
||||
*/
|
||||
onResize: ?Function,
|
||||
onResize?: Function,
|
||||
|
||||
/**
|
||||
* Callback to invoke on message send.
|
||||
*/
|
||||
onSend: Function,
|
||||
onSend: Function
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* Whether chat emoticons are disabled.
|
||||
*/
|
||||
_areSmileysDisabled: boolean
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link ChatInput}.
|
||||
|
@ -67,7 +66,7 @@ type State = {
|
|||
* @augments Component
|
||||
*/
|
||||
class ChatInput extends Component<Props, State> {
|
||||
_textArea: ?HTMLTextAreaElement;
|
||||
_textArea?: HTMLTextAreaElement;
|
||||
|
||||
state = {
|
||||
message: '',
|
||||
|
@ -83,17 +82,14 @@ class ChatInput extends Component<Props, State> {
|
|||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._textArea = null;
|
||||
this._textArea = undefined;
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDetectSubmit = this._onDetectSubmit.bind(this);
|
||||
this._onMessageChange = this._onMessageChange.bind(this);
|
||||
this._onSmileySelect = this._onSmileySelect.bind(this);
|
||||
this._onSubmitMessage = this._onSubmitMessage.bind(this);
|
||||
this._onToggleSmileysPanel = this._onToggleSmileysPanel.bind(this);
|
||||
this._onEscHandler = this._onEscHandler.bind(this);
|
||||
this._onToggleSmileysPanelKeyPress = this._onToggleSmileysPanelKeyPress.bind(this);
|
||||
this._onSubmitMessageKeyPress = this._onSubmitMessageKeyPress.bind(this);
|
||||
this._toggleSmileysPanel = this._toggleSmileysPanel.bind(this);
|
||||
this._setTextAreaRef = this._setTextAreaRef.bind(this);
|
||||
}
|
||||
|
||||
|
@ -116,62 +112,37 @@ class ChatInput extends Component<Props, State> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const smileysPanelClassName = `${this.state.showSmileysPanel
|
||||
? 'show-smileys' : 'hide-smileys'} smileys-panel`;
|
||||
|
||||
return (
|
||||
<div className = { `chat-input-container${this.state.message.trim().length ? ' populated' : ''}` }>
|
||||
<div id = 'chat-input' >
|
||||
{ this.props._areSmileysDisabled ? null : (
|
||||
<div className = 'smiley-input'>
|
||||
<div id = 'smileysarea'>
|
||||
<div id = 'smileys'>
|
||||
<div
|
||||
aria-expanded = { this.state.showSmileysPanel }
|
||||
aria-haspopup = 'smileysContainer'
|
||||
aria-label = { this.props.t('chat.smileysPanel') }
|
||||
className = 'smiley-button'
|
||||
onClick = { this._onToggleSmileysPanel }
|
||||
onKeyDown = { this._onEscHandler }
|
||||
onKeyPress = { this._onToggleSmileysPanelKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<Icon src = { IconSmile } />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!this.props._areSmileysDisabled && this.state.showSmileysPanel && (
|
||||
<div
|
||||
className = 'smiley-input'>
|
||||
<div
|
||||
className = { smileysPanelClassName } >
|
||||
className = 'smileys-panel' >
|
||||
<SmileysPanel
|
||||
onSmileySelect = { this._onSmileySelect } />
|
||||
</div>
|
||||
</div>
|
||||
) }
|
||||
<div className = 'usrmsg-form'>
|
||||
<TextareaAutosize
|
||||
autoComplete = 'off'
|
||||
autoFocus = { true }
|
||||
id = 'usermsg'
|
||||
maxRows = { 5 }
|
||||
onChange = { this._onMessageChange }
|
||||
onHeightChange = { this.props.onResize }
|
||||
onKeyDown = { this._onDetectSubmit }
|
||||
placeholder = { this.props.t('chat.messagebox') }
|
||||
ref = { this._setTextAreaRef }
|
||||
tabIndex = { 0 }
|
||||
value = { this.state.message } />
|
||||
</div>
|
||||
<div className = 'send-button-container'>
|
||||
<div
|
||||
aria-label = { this.props.t('chat.sendButton') }
|
||||
className = 'send-button'
|
||||
onClick = { this._onSubmitMessage }
|
||||
onKeyPress = { this._onSubmitMessageKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { this.state.message.trim() ? 0 : -1 } >
|
||||
<Icon src = { IconPlane } />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Input
|
||||
autoFocus = { true }
|
||||
className = 'chat-input'
|
||||
icon = { this.props._areSmileysDisabled ? undefined : IconSmile }
|
||||
iconClick = { this._toggleSmileysPanel }
|
||||
maxRows = { 5 }
|
||||
onChange = { this._onMessageChange }
|
||||
onKeyPress = { this._onDetectSubmit }
|
||||
placeholder = { this.props.t('chat.messagebox') }
|
||||
ref = { this._setTextAreaRef }
|
||||
textarea = { true }
|
||||
value = { this.state.message } />
|
||||
<Button
|
||||
accessibilityLabel = { this.props.t('chat.sendButton') }
|
||||
disabled = { !this.state.message.trim() }
|
||||
icon = { IconPlane }
|
||||
onClick = { this._onSubmitMessage }
|
||||
size = { isMobileBrowser() ? 'large' : 'medium' } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -187,9 +158,6 @@ class ChatInput extends Component<Props, State> {
|
|||
this._textArea && this._textArea.focus();
|
||||
}
|
||||
|
||||
|
||||
_onSubmitMessage: () => void;
|
||||
|
||||
/**
|
||||
* Submits the message to the chat window.
|
||||
*
|
||||
|
@ -208,7 +176,6 @@ class ChatInput extends Component<Props, State> {
|
|||
}
|
||||
|
||||
}
|
||||
_onDetectSubmit: (Object) => void;
|
||||
|
||||
/**
|
||||
* Detects if enter has been pressed. If so, submit the message in the chat
|
||||
|
@ -218,7 +185,7 @@ class ChatInput extends Component<Props, State> {
|
|||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDetectSubmit(event) {
|
||||
_onDetectSubmit(event: any) {
|
||||
// Composition events used to add accents to characters
|
||||
// despite their absence from standard US keyboards,
|
||||
// to build up logograms of many Asian languages
|
||||
|
@ -241,37 +208,17 @@ class ChatInput extends Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
_onSubmitMessageKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSubmitMessageKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onSubmitMessage();
|
||||
}
|
||||
}
|
||||
|
||||
_onMessageChange: (Object) => void;
|
||||
|
||||
/**
|
||||
* Updates the known message the user is drafting.
|
||||
*
|
||||
* @param {string} event - Keyboard event.
|
||||
* @param {string} value - Keyboard event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onMessageChange(event) {
|
||||
this.setState({ message: event.target.value });
|
||||
_onMessageChange(value: string) {
|
||||
this.setState({ message: value });
|
||||
}
|
||||
|
||||
_onSmileySelect: (string) => void;
|
||||
|
||||
/**
|
||||
* Appends a selected smileys to the chat message draft.
|
||||
*
|
||||
|
@ -280,7 +227,7 @@ class ChatInput extends Component<Props, State> {
|
|||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSmileySelect(smileyText) {
|
||||
_onSmileySelect(smileyText: string) {
|
||||
if (smileyText) {
|
||||
this.setState({
|
||||
message: `${this.state.message} ${smileyText}`,
|
||||
|
@ -295,57 +242,19 @@ class ChatInput extends Component<Props, State> {
|
|||
this._focus();
|
||||
}
|
||||
|
||||
_onToggleSmileysPanel: () => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to hide or show the smileys selector.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToggleSmileysPanel() {
|
||||
_toggleSmileysPanel() {
|
||||
if (this.state.showSmileysPanel) {
|
||||
this._focus();
|
||||
}
|
||||
this.setState({ showSmileysPanel: !this.state.showSmileysPanel });
|
||||
}
|
||||
|
||||
_onEscHandler: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onEscHandler(e) {
|
||||
// Escape handling does not work in onKeyPress
|
||||
if (this.state.showSmileysPanel && e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this._onToggleSmileysPanel();
|
||||
}
|
||||
}
|
||||
|
||||
_onToggleSmileysPanelKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToggleSmileysPanelKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onToggleSmileysPanel();
|
||||
}
|
||||
}
|
||||
|
||||
_setTextAreaRef: (?HTMLTextAreaElement) => void;
|
||||
|
||||
/**
|
||||
* Sets the reference to the HTML TextArea.
|
||||
*
|
||||
|
@ -353,7 +262,7 @@ class ChatInput extends Component<Props, State> {
|
|||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setTextAreaRef(textAreaElement: ?HTMLTextAreaElement) {
|
||||
_setTextAreaRef(textAreaElement?: HTMLTextAreaElement) {
|
||||
this._textArea = textAreaElement;
|
||||
}
|
||||
}
|
||||
|
@ -367,7 +276,7 @@ class ChatInput extends Component<Props, State> {
|
|||
* _areSmileysDisabled: boolean
|
||||
* }}
|
||||
*/
|
||||
const mapStateToProps = state => {
|
||||
const mapStateToProps = (state: IState) => {
|
||||
return {
|
||||
_areSmileysDisabled: areSmileysDisabled(state)
|
||||
};
|
Loading…
Reference in New Issue