feat(prejoin) Update design & refactor (#13089)

Update Dial Out Dialog design
Update Country Picker design
Convert some files to TS
Move styles from SCSS to JSS
Replace atlaskit InlineDialog with Popover in CountryPIcker and Prejoin components
This commit is contained in:
Robert Pintilii
2023-03-23 11:45:29 +02:00
committed by GitHub
parent c6213eb160
commit d7cad9d560
13 changed files with 347 additions and 466 deletions

View File

@@ -1,4 +1,3 @@
import InlineDialog from '@atlaskit/inline-dialog';
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
@@ -10,6 +9,7 @@ import { translate } from '../../../base/i18n/functions';
import { IconArrowDown, IconArrowUp, IconPhoneRinging, IconVolumeOff } from '../../../base/icons/svg';
import { isVideoMutedByUser } from '../../../base/media/functions';
import { getLocalParticipant } from '../../../base/participants/functions';
import Popover from '../../../base/popover/components/Popover.web';
import ActionButton from '../../../base/premeeting/components/web/ActionButton';
import PreMeetingScreen from '../../../base/premeeting/components/web/PreMeetingScreen';
import { updateSettings } from '../../../base/settings/actions';
@@ -373,7 +373,7 @@ class Prejoin extends Component<IProps, IState> {
data-testid = 'prejoin.errorMessage'>{t('prejoin.errorMissingName')}</div>}
<div className = 'prejoin-preview-dropdown-container'>
<InlineDialog
<Popover
content = { hasExtraJoinButtons && <div className = 'prejoin-preview-dropdown-btns'>
{extraButtonsToRender.map(({ key, ...rest }) => (
<Button
@@ -384,8 +384,10 @@ class Prejoin extends Component<IProps, IState> {
{ ...rest } />
))}
</div> }
isOpen = { showJoinByPhoneButtons }
onClose = { _onDropdownClose }>
onPopoverClose = { _onDropdownClose }
position = 'bottom'
trigger = 'click'
visible = { showJoinByPhoneButtons }>
<ActionButton
OptionsIcon = { showJoinByPhoneButtons ? IconArrowUp : IconArrowDown }
ariaDropDownLabel = { t('prejoin.joinWithoutAudio') }
@@ -401,7 +403,7 @@ class Prejoin extends Component<IProps, IState> {
type = 'primary'>
{ t('prejoin.joinMeeting') }
</ActionButton>
</InlineDialog>
</Popover>
</div>
</div>
{ showDialog && (

View File

@@ -1,27 +1,39 @@
// @flow
import React from 'react';
import { makeStyles } from 'tss-react/mui';
import { countries } from '../../../utils';
import CountryRow from './CountryRow';
type Props = {
interface IProps {
/**
* Click handler for a single entry.
*/
onEntryClick: Function,
};
onEntryClick: Function;
}
const useStyles = makeStyles()(theme => {
return {
container: {
height: '190px',
width: '343px',
overflowY: 'auto',
backgroundColor: theme.palette.ui01
}
};
});
/**
* This component displays the dropdown for the country picker.
*
* @returns {ReactElement}
*/
function CountryDropdown({ onEntryClick }: Props) {
function CountryDropdown({ onEntryClick }: IProps) {
const { classes } = useStyles();
return (
<div className = 'cpick-dropdown'>
<div className = { classes.container }>
{countries.map(country => (
<CountryRow
country = { country }

View File

@@ -1,249 +0,0 @@
// @flow
import InlineDialog from '@atlaskit/inline-dialog';
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { setDialOutCountry, setDialOutNumber } from '../../../actions.web';
import { getDialOutCountry, getDialOutNumber } from '../../../functions';
import { getCountryFromDialCodeText } from '../../../utils';
import CountryDropDown from './CountryDropdown';
import CountrySelector from './CountrySelector';
const PREFIX_REG = /^(00)|\+/;
type Props = {
/**
* The country to dial out to.
*/
dialOutCountry: { name: string, dialCode: string, code: string },
/**
* The number to dial out to.
*/
dialOutNumber: string,
/**
* Handler used when user presses 'Enter'.
*/
onSubmit: Function,
/**
* Sets the dial out number.
*/
setDialOutNumber: Function,
/**
* Sets the dial out country.
*/
setDialOutCountry: Function,
};
type State = {
/**
* If the country picker is open or not.
*/
isOpen: boolean,
/**
* The value of the input.
*/
value: string
}
/**
* This component displays a country picker with an input for the phone number.
*/
class CountryPicker extends PureComponent<Props, State> {
/**
* A React ref to the HTML element containing the {@code input} instance.
*/
inputRef: Object;
/**
* Initializes a new {@code CountryPicker} instance.
*
* @inheritdoc
*/
constructor(props) {
super(props);
this.state = {
isOpen: false,
value: ''
};
this.inputRef = React.createRef();
this._onChange = this._onChange.bind(this);
this._onDropdownClose = this._onDropdownClose.bind(this);
this._onCountrySelectorClick = this._onCountrySelectorClick.bind(this);
this._onEntryClick = this._onEntryClick.bind(this);
this._onKeyPress = this._onKeyPress.bind(this);
}
/**
* Implements React's {@link Component#componentDidUnmount()}.
*
* @inheritdoc
*/
componentDidMount() {
this.inputRef.current.focus();
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { dialOutCountry, dialOutNumber } = this.props;
const { isOpen } = this.state;
const {
inputRef,
_onChange,
_onCountrySelectorClick,
_onDropdownClose,
_onKeyPress,
_onEntryClick
} = this;
return (
<div className = 'cpick-container'>
<InlineDialog
content = { <CountryDropDown onEntryClick = { _onEntryClick } /> }
isOpen = { isOpen }
onClose = { _onDropdownClose }>
<div className = 'cpick'>
<CountrySelector
country = { dialOutCountry }
onClick = { _onCountrySelectorClick } />
<input
className = 'cpick-input'
onChange = { _onChange }
onKeyPress = { _onKeyPress }
ref = { inputRef }
value = { dialOutNumber } />
</div>
</InlineDialog>
</div>
);
}
_onChange: (Object) => void;
/**
* Handles the input text change.
* Automatically updates the country from the 'CountrySelector' if a
* phone number prefix is entered (00 or +).
*
* @param {Object} e - The synthetic event.
* @returns {void}
*/
_onChange({ target: { value } }) {
if (PREFIX_REG.test(value)) {
const textWithDialCode = value.replace(PREFIX_REG, '');
if (textWithDialCode.length >= 4) {
const country = getCountryFromDialCodeText(textWithDialCode);
if (country) {
const rest = textWithDialCode.replace(country.dialCode, '');
this.props.setDialOutCountry(country);
this.props.setDialOutNumber(rest);
return;
}
}
}
this.props.setDialOutNumber(value);
}
_onCountrySelectorClick: (Object) => void;
/**
* Click handler for country selector.
*
* @param {Object} e - The synthetic event.
* @returns {void}
*/
_onCountrySelectorClick() {
this.setState({
isOpen: !this.setState.isOpen
});
}
_onDropdownClose: () => void;
/**
* Closes the dropdown.
*
* @returns {void}
*/
_onDropdownClose() {
this.setState({
isOpen: false
});
}
_onEntryClick: (Object) => void;
/**
* Click handler for a single entry from the dropdown.
*
* @param {Object} country - The country used for dialing out.
* @returns {void}
*/
_onEntryClick(country) {
this.props.setDialOutCountry(country);
this._onDropdownClose();
}
_onKeyPress: (Object) => void;
/**
* Handler for key presses.
*
* @param {Object} e - The synthetic event.
* @returns {void}
*/
_onKeyPress(e) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
this.props.onSubmit();
}
}
}
/**
* Maps (parts of) the redux state to the React {@code Component} props.
*
* @param {Object} state - The redux state.
* @returns {Props}
*/
function mapStateToProps(state) {
return {
dialOutCountry: getDialOutCountry(state),
dialOutNumber: getDialOutNumber(state)
};
}
/**
* Maps redux actions to the props of the component.
*
* @type {{
* setDialOutCountry: Function,
* setDialOutNumber: Function
* }}
*/
const mapDispatchToProps = {
setDialOutCountry,
setDialOutNumber
};
export default connect(mapStateToProps, mapDispatchToProps)(CountryPicker);

View File

@@ -0,0 +1,164 @@
import React, { useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../../app/types';
import Popover from '../../../../base/popover/components/Popover.web';
import { withPixelLineHeight } from '../../../../base/styles/functions.web';
import { setDialOutCountry, setDialOutNumber } from '../../../actions.web';
import { getDialOutCountry, getDialOutNumber } from '../../../functions';
import { getCountryFromDialCodeText } from '../../../utils';
import CountryDropDown from './CountryDropdown';
import CountrySelector from './CountrySelector';
const PREFIX_REG = /^(00)|\+/;
interface IProps {
/**
* The country to dial out to.
*/
dialOutCountry: { code: string; dialCode: string; name: string; };
/**
* The number to dial out to.
*/
dialOutNumber: string;
/**
* Handler used when user presses 'Enter'.
*/
onSubmit: Function;
/**
* Sets the dial out country.
*/
setDialOutCountry: Function;
/**
* Sets the dial out number.
*/
setDialOutNumber: Function;
}
const useStyles = makeStyles()(theme => {
return {
container: {
border: 0,
borderRadius: theme.shape.borderRadius,
display: 'flex',
backgroundColor: theme.palette.ui03
},
input: {
padding: '0 4px',
margin: 0,
border: 0,
background: 'transparent',
color: theme.palette.text01,
flexGrow: 1,
...withPixelLineHeight(theme.typography.bodyShortRegular)
}
};
});
const CountryPicker = (props: IProps) => {
const [ isOpen, setIsOpen ] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const { classes } = useStyles();
useEffect(() => {
inputRef.current?.focus();
}, []);
const onChange = ({ target: { value: newValue } }: React.ChangeEvent<HTMLInputElement>) => {
if (PREFIX_REG.test(newValue)) {
const textWithDialCode = newValue.replace(PREFIX_REG, '');
if (textWithDialCode.length >= 4) {
const country = getCountryFromDialCodeText(textWithDialCode);
if (country) {
const rest = textWithDialCode.replace(country.dialCode, '');
props.setDialOutCountry(country);
props.setDialOutNumber(rest);
return;
}
}
}
props.setDialOutNumber(newValue);
};
const onCountrySelectorClick = () => {
setIsOpen(open => !open);
};
const onDropdownClose = () => {
setIsOpen(false);
};
const onEntryClick = (country: { code: string; dialCode: string; name: string; }) => {
props.setDialOutCountry(country);
onDropdownClose();
};
const onKeyPress = (e: React.KeyboardEvent) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
props.onSubmit();
}
};
return (
/* eslint-disable react/jsx-no-bind */
<Popover
content = { <CountryDropDown onEntryClick = { onEntryClick } /> }
onPopoverClose = { onDropdownClose }
position = 'bottom'
trigger = 'click'
visible = { isOpen }>
<div className = { classes.container }>
<CountrySelector
country = { props.dialOutCountry }
onClick = { onCountrySelectorClick } />
<input
className = { classes.input }
onChange = { onChange }
onKeyPress = { onKeyPress }
ref = { inputRef }
value = { props.dialOutNumber } />
</div>
</Popover>
);
};
/**
* Maps (parts of) the redux state to the React {@code Component} props.
*
* @param {Object} state - The redux state.
* @returns {IProps}
*/
function mapStateToProps(state: IReduxState) {
return {
dialOutCountry: getDialOutCountry(state),
dialOutNumber: getDialOutNumber(state)
};
}
/**
* Maps redux actions to the props of the component.
*
* @type {{
* setDialOutCountry: Function,
* setDialOutNumber: Function
* }}
*/
const mapDispatchToProps = {
setDialOutCountry,
setDialOutNumber
};
export default connect(mapStateToProps, mapDispatchToProps)(CountryPicker);

View File

@@ -1,68 +0,0 @@
// @flow
import React, { PureComponent } from 'react';
type Props = {
/**
* Country of the entry.
*/
country: { name: string, dialCode: string, code: string },
/**
* Entry click handler.
*/
onEntryClick: Function,
};
/**
* This component displays a row from the country picker dropdown.
*/
class CountryRow extends PureComponent<Props> {
/**
* Initializes a new {@code CountryRow} instance.
*
* @param {Props} props - The props of the component.
*/
constructor(props: Props) {
super(props);
this._onClick = this._onClick.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
country: { code, dialCode, name }
} = this.props;
return (
<div
className = 'cpick-dropdown-entry'
onClick = { this._onClick }>
<div className = { `prejoin-dialog-flag iti-flag ${code}` } />
<div className = 'cpick-dropdown-entry-text'>
{`${name} (+${dialCode})`}
</div>
</div>
);
}
_onClick: () => void;
/**
* Click handler.
*
* @returns {void}
*/
_onClick() {
this.props.onEntryClick(this.props.country);
}
}
export default CountryRow;

View File

@@ -0,0 +1,67 @@
import React from 'react';
import { makeStyles } from 'tss-react/mui';
import { withPixelLineHeight } from '../../../../base/styles/functions.web';
interface IProps {
/**
* Country of the entry.
*/
country: { code: string; dialCode: string; name: string; };
/**
* Entry click handler.
*/
onEntryClick: Function;
}
const useStyles = makeStyles()(theme => {
return {
container: {
display: 'flex',
padding: '10px',
alignItems: 'center',
backgroundColor: theme.palette.action03,
'&:hover': {
backgroundColor: theme.palette.action03Hover
}
},
flag: {
marginRight: theme.spacing(2)
},
text: {
color: theme.palette.text01,
...withPixelLineHeight(theme.typography.bodyShortRegular),
flexGrow: 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
};
});
const CountryRow = ({ country, onEntryClick }: IProps) => {
const { classes, cx } = useStyles();
const _onClick = () => {
onEntryClick(country);
};
return (
<div
className = { classes.container }
// eslint-disable-next-line react/jsx-no-bind
onClick = { _onClick }>
<div className = { cx(classes.flag, 'iti-flag', country.code) } />
<div className = { classes.text }>
{`${country.name} (+${country.dialCode})`}
</div>
</div>
);
};
export default CountryRow;

View File

@@ -1,48 +0,0 @@
// @flow
import React, { useCallback } from 'react';
import { Icon, IconArrowDown } from '../../../../base/icons';
type Props = {
/**
* Country object of the entry.
*/
country: { name: string, dialCode: string, code: string },
/**
* Click handler for the selector.
*/
onClick: Function,
};
/**
* This component displays the country selector with the flag.
*
* @returns {ReactElement}
*/
function CountrySelector({ country: { code, dialCode }, onClick }: Props) {
const onKeyPressHandler = useCallback(e => {
if (onClick && (e.key === ' ' || e.key === 'Enter')) {
e.preventDefault();
onClick();
}
}, [ onClick ]);
return (
<div
className = 'cpick-selector'
onClick = { onClick }
onKeyPress = { onKeyPressHandler }>
<div className = { `prejoin-dialog-flag iti-flag ${code}` } />
<span>{`+${dialCode}`}</span>
<Icon
className = 'cpick-icon'
size = { 16 }
src = { IconArrowDown } />
</div>
);
}
export default CountrySelector;

View File

@@ -0,0 +1,77 @@
import React, { useCallback } from 'react';
import { makeStyles } from 'tss-react/mui';
import Icon from '../../../../base/icons/components/Icon';
import { IconArrowDown } from '../../../../base/icons/svg';
import { withPixelLineHeight } from '../../../../base/styles/functions.web';
interface IProps {
/**
* Country object of the entry.
*/
country: { code: string; dialCode: string; name: string; };
/**
* Click handler for the selector.
*/
onClick: () => void;
}
const useStyles = makeStyles()(theme => {
return {
container: {
padding: '8px 10px',
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
backgroundColor: theme.palette.ui01,
borderRight: `1px solid ${theme.palette.ui03}`,
color: theme.palette.text01,
...withPixelLineHeight(theme.typography.bodyShortRegular),
position: 'relative',
width: '88px',
borderTopLeftRadius: theme.shape.borderRadius,
borderBottomLeftRadius: theme.shape.borderRadius
},
text: {
flexGrow: 1
},
flag: {
marginRight: theme.spacing(2)
}
};
});
/**
* This component displays the country selector with the flag.
*
* @returns {ReactElement}
*/
function CountrySelector({ country: { code, dialCode }, onClick }: IProps) {
const { classes, cx } = useStyles();
const onKeyPressHandler = useCallback(e => {
if (onClick && (e.key === ' ' || e.key === 'Enter')) {
e.preventDefault();
onClick();
}
}, [ onClick ]);
return (
<div
className = { classes.container }
onClick = { onClick }
onKeyPress = { onKeyPressHandler }>
<div className = { cx(classes.flag, 'iti-flag', code) } />
<span className = { classes.text }>{`+${dialCode}`}</span>
<Icon
size = { 16 }
src = { IconArrowDown } />
</div>
);
}
export default CountrySelector;