ref(prejoin) Refactor styles (#13187)

Move styles from SCSS to JSS
Convert class to function component
pull/13195/head jitsi-meet_8537
Robert Pintilii 2 years ago committed by GitHub
parent 57dbd3cf54
commit 5a64bd76fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      css/premeeting/_lobby.scss
  2. 1
      css/premeeting/_main.scss
  3. 73
      css/premeeting/_prejoin.scss
  4. 6
      react/features/lobby/components/web/LobbyScreen.js
  5. 407
      react/features/prejoin/components/web/Prejoin.tsx
  6. 3
      react/features/prejoin/components/web/dialogs/JoinByPhoneDialog.tsx

@ -208,3 +208,26 @@
.lobby-button-margin {
margin-bottom: 16px;
}
.lobby-prejoin-error {
background-color: #E04757;
border-radius: 6px;
box-sizing: border-box;
color: white;
font-size: 12px;
line-height: 16px;
margin-bottom: 16px;
margin-top: -8px;
padding: 4px;
text-align: center;
width: 100%;
}
.lobby-prejoin-input {
margin-bottom: 16px;
width: 100%;
& input {
text-align: center;
}
}

@ -1,4 +1,3 @@
@import 'lobby';
@import 'premeeting-screens';
@import 'prejoin';
@import 'prejoin-third-party';

@ -1,73 +0,0 @@
.prejoin {
&-input-area {
width: 100%;
}
&-avatar {
margin: 8px auto 16px;
&-name {
color: white;
font-size: 16px;
font-weight: 600;
line-height: 26px;
margin-bottom: 32px;
text-align: center;
}
&-container {
align-items: center;
display: flex;
flex-direction: column;
}
}
&-error {
background-color: #E04757;
border-radius: 6px;
box-sizing: border-box;
color: white;
font-size: 12px;
line-height: 16px;
margin-bottom: 16px;
margin-top: -8px;
padding: 4px;
text-align: center;
width: 100%;
}
}
.prejoin-preview {
&-dropdown-btns {
padding: 8px 0;
width: 300px;
background-color: #E0E0E0;
border-radius: 3px;
position: relative;
top: -16px;
}
&-dropdown-container {
position: relative;
width: 100%;
/**
* Override default InlineDialog behaviour, since it does not play nicely with relative widths
*/
& > div:nth-child(2) {
background: #E0E0E0;
padding: 0;
position: absolute !important;
width: 100%;
}
}
}
.prejoin-input {
margin-bottom: 16px;
width: 100%;
& input {
text-align: center;
}
}

@ -187,7 +187,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
return (
<Input
className = 'prejoin-input'
className = 'lobby-prejoin-input'
onChange = { this._onChangeDisplayName }
placeholder = { t('lobby.nameField') }
testId = 'lobby.nameField'
@ -206,7 +206,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
return (
<>
<Input
className = { `prejoin-input ${_passwordJoinFailed ? 'error' : ''}` }
className = { `lobby-prejoin-input ${_passwordJoinFailed ? 'error' : ''}` }
onChange = { this._onChangePassword }
placeholder = { t('lobby.passwordField') }
testId = 'lobby.password'
@ -214,7 +214,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
value = { this.state.password } />
{_passwordJoinFailed && <div
className = 'prejoin-error'
className = 'lobby-prejoin-error'
data-testid = 'lobby.errorMessage'>{t('lobby.invalidPassword')}</div>}
</>
);

@ -1,11 +1,12 @@
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
/* eslint-disable react/jsx-no-bind */
import React, { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../app/types';
import Avatar from '../../../base/avatar/components/Avatar';
import { isNameReadOnly } from '../../../base/config/functions.web';
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';
@ -14,6 +15,7 @@ import ActionButton from '../../../base/premeeting/components/web/ActionButton';
import PreMeetingScreen from '../../../base/premeeting/components/web/PreMeetingScreen';
import { updateSettings } from '../../../base/settings/actions';
import { getDisplayName } from '../../../base/settings/functions.web';
import { withPixelLineHeight } from '../../../base/styles/functions.web';
import { getLocalJitsiVideoTrack } from '../../../base/tracks/functions.web';
import Button from '../../../base/ui/components/web/Button';
import Input from '../../../base/ui/components/web/Input';
@ -33,7 +35,7 @@ import {
import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
interface IProps extends WithTranslation {
interface IProps {
/**
* Indicates whether the display name is editable.
@ -116,45 +118,90 @@ interface IProps extends WithTranslation {
videoTrack?: Object;
}
interface IState {
/**
* Flag controlling the visibility of the 'join by phone' buttons.
*/
showJoinByPhoneButtons: boolean;
}
/**
* This component is displayed before joining a meeting.
*/
class Prejoin extends Component<IProps, IState> {
showDisplayNameField: boolean;
/**
* Initializes a new {@code Prejoin} instance.
*
* @inheritdoc
*/
constructor(props: IProps) {
super(props);
this.state = {
showJoinByPhoneButtons: false
};
this._closeDialog = this._closeDialog.bind(this);
this._showDialog = this._showDialog.bind(this);
this._onJoinButtonClick = this._onJoinButtonClick.bind(this);
this._onDropdownClose = this._onDropdownClose.bind(this);
this._onOptionsClick = this._onOptionsClick.bind(this);
this._setName = this._setName.bind(this);
this._onJoinConferenceWithoutAudioKeyPress = this._onJoinConferenceWithoutAudioKeyPress.bind(this);
this._showDialogKeyPress = this._showDialogKeyPress.bind(this);
this._getExtraJoinButtons = this._getExtraJoinButtons.bind(this);
this._onInputKeyPress = this._onInputKeyPress.bind(this);
this.showDisplayNameField = props.canEditDisplayName || props.showErrorOnJoin;
}
const useStyles = makeStyles()(theme => {
return {
inputContainer: {
width: '100%'
},
input: {
width: '100%',
marginBottom: theme.spacing(3),
'& input': {
textAlign: 'center'
}
},
avatarContainer: {
display: 'flex',
alignItems: 'center',
flexDirection: 'column'
},
avatar: {
margin: `${theme.spacing(2)} auto ${theme.spacing(3)}`
},
avatarName: {
...withPixelLineHeight(theme.typography.bodyShortBoldLarge),
color: theme.palette.text01,
marginBottom: theme.spacing(5),
textAlign: 'center'
},
error: {
backgroundColor: theme.palette.actionDanger,
color: theme.palette.text01,
borderRadius: theme.shape.borderRadius,
width: '100%',
...withPixelLineHeight(theme.typography.labelRegular),
boxSizing: 'border-box',
padding: theme.spacing(1),
textAlign: 'center',
marginTop: `-${theme.spacing(2)}`,
marginBottom: theme.spacing(3)
},
dropdownContainer: {
position: 'relative',
width: '100%'
},
dropdownButtons: {
width: '300px',
padding: '8px 0',
backgroundColor: theme.palette.action02,
color: theme.palette.text04,
borderRadius: theme.shape.borderRadius,
position: 'relative',
top: `-${theme.spacing(3)}`
}
};
});
const Prejoin = ({
canEditDisplayName,
deviceStatusVisible,
hasJoinByPhoneButton,
joinConference,
joinConferenceWithoutAudio,
joiningInProgress,
name,
participantId,
prejoinConfig,
readOnlyName,
setJoinByPhoneDialogVisiblity,
showCameraPreview,
showDialog,
showErrorOnJoin,
updateSettings: dispatchUpdateSettings,
videoTrack
}: IProps) => {
const showDisplayNameField = useRef(canEditDisplayName || showErrorOnJoin);
const [ showJoinByPhoneButtons, setShowJoinByPhoneButtons ] = useState(false);
const { classes } = useStyles();
const { t } = useTranslation();
/**
* Handler for the join button.
@ -162,23 +209,21 @@ class Prejoin extends Component<IProps, IState> {
* @param {Object} e - The synthetic event.
* @returns {void}
*/
_onJoinButtonClick() {
if (this.props.showErrorOnJoin) {
const onJoinButtonClick = () => {
if (showErrorOnJoin) {
return;
}
this.props.joinConference();
}
joinConference();
};
/**
* Closes the dropdown.
*
* @returns {void}
*/
_onDropdownClose() {
this.setState({
showJoinByPhoneButtons: false
});
}
const onDropdownClose = () => {
setShowJoinByPhoneButtons(false);
};
/**
* Displays the join by phone buttons dropdown.
@ -186,13 +231,11 @@ class Prejoin extends Component<IProps, IState> {
* @param {Object} e - The synthetic event.
* @returns {void}
*/
_onOptionsClick(e?: React.KeyboardEvent | React.MouseEvent | undefined) {
const onOptionsClick = (e?: React.KeyboardEvent | React.MouseEvent | undefined) => {
e?.stopPropagation();
this.setState({
showJoinByPhoneButtons: !this.state.showJoinByPhoneButtons
});
}
setShowJoinByPhoneButtons(show => !show);
};
/**
* Sets the guest participant name.
@ -200,30 +243,30 @@ class Prejoin extends Component<IProps, IState> {
* @param {string} displayName - Participant name.
* @returns {void}
*/
_setName(displayName: string) {
this.props.updateSettings({
const setName = (displayName: string) => {
dispatchUpdateSettings({
displayName
});
}
};
/**
* Closes the join by phone dialog.
*
* @returns {undefined}
*/
_closeDialog() {
this.props.setJoinByPhoneDialogVisiblity(false);
}
const closeDialog = () => {
setJoinByPhoneDialogVisiblity(false);
};
/**
* Displays the dialog for joining a meeting by phone.
*
* @returns {undefined}
*/
_showDialog() {
this.props.setJoinByPhoneDialogVisiblity(true);
this._onDropdownClose();
}
const doShowDialog = () => {
setJoinByPhoneDialogVisiblity(true);
onDropdownClose();
};
/**
* KeyPress handler for accessibility.
@ -232,12 +275,12 @@ class Prejoin extends Component<IProps, IState> {
*
* @returns {void}
*/
_showDialogKeyPress(e: React.KeyboardEvent) {
const showDialogKeyPress = (e: React.KeyboardEvent) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
this._showDialog();
doShowDialog();
}
}
};
/**
* KeyPress handler for accessibility.
@ -246,30 +289,28 @@ class Prejoin extends Component<IProps, IState> {
*
* @returns {void}
*/
_onJoinConferenceWithoutAudioKeyPress(e: React.KeyboardEvent) {
if (this.props.joinConferenceWithoutAudio
const onJoinConferenceWithoutAudioKeyPress = (e: React.KeyboardEvent) => {
if (joinConferenceWithoutAudio
&& (e.key === ' '
|| e.key === 'Enter')) {
e.preventDefault();
this.props.joinConferenceWithoutAudio();
joinConferenceWithoutAudio();
}
}
};
/**
* Gets the list of extra join buttons.
*
* @returns {Object} - The list of extra buttons.
*/
_getExtraJoinButtons() {
const { joinConferenceWithoutAudio, t } = this.props;
const getExtraJoinButtons = () => {
const noAudio = {
key: 'no-audio',
testId: 'prejoin.joinWithoutAudio',
icon: IconVolumeOff,
label: t('prejoin.joinWithoutAudio'),
onClick: joinConferenceWithoutAudio,
onKeyPress: this._onJoinConferenceWithoutAudioKeyPress
onKeyPress: onJoinConferenceWithoutAudioKeyPress
};
const byPhone = {
@ -277,15 +318,15 @@ class Prejoin extends Component<IProps, IState> {
testId: 'prejoin.joinByPhone',
icon: IconPhoneRinging,
label: t('prejoin.joinAudioByPhone'),
onClick: this._showDialog,
onKeyPress: this._showDialogKeyPress
onClick: doShowDialog,
onKeyPress: showDialogKeyPress
};
return {
noAudio,
byPhone
};
}
};
/**
* Handle keypress on input.
@ -293,127 +334,99 @@ class Prejoin extends Component<IProps, IState> {
* @param {KeyboardEvent} e - Keyboard event.
* @returns {void}
*/
_onInputKeyPress(e: React.KeyboardEvent) {
const { joinConference } = this.props;
const onInputKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
joinConference();
}
}
};
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
deviceStatusVisible,
hasJoinByPhoneButton,
joinConferenceWithoutAudio,
joiningInProgress,
name,
participantId,
prejoinConfig,
readOnlyName,
showCameraPreview,
showDialog,
showErrorOnJoin,
t,
videoTrack
} = this.props;
const { _closeDialog, _onDropdownClose, _onJoinButtonClick,
_onOptionsClick, _setName, _onInputKeyPress } = this;
const extraJoinButtons = this._getExtraJoinButtons();
let extraButtonsToRender = Object.values(extraJoinButtons).filter((val: any) =>
!(prejoinConfig?.hideExtraJoinButtons || []).includes(val.key)
);
if (!hasJoinByPhoneButton) {
extraButtonsToRender = extraButtonsToRender.filter((btn: any) => btn.key !== 'by-phone');
}
const hasExtraJoinButtons = Boolean(extraButtonsToRender.length);
const { showJoinByPhoneButtons } = this.state;
return (
<PreMeetingScreen
showDeviceStatus = { deviceStatusVisible }
title = { t('prejoin.joinMeeting') }
videoMuted = { !showCameraPreview }
videoTrack = { videoTrack }>
<div
className = 'prejoin-input-area'
data-testid = 'prejoin.screen'>
{this.showDisplayNameField ? (<Input
autoComplete = { 'name' }
autoFocus = { true }
className = 'prejoin-input'
error = { showErrorOnJoin }
onChange = { _setName }
onKeyPress = { _onInputKeyPress }
placeholder = { t('dialog.enterDisplayName') }
readOnly = { readOnlyName }
value = { name } />
) : (
<div className = 'prejoin-avatar-container'>
<Avatar
className = 'prejoin-avatar'
displayName = { name }
participantId = { participantId }
size = { 72 } />
<div className = 'prejoin-avatar-name'>{name}</div>
</div>
)}
{showErrorOnJoin && <div
className = 'prejoin-error'
data-testid = 'prejoin.errorMessage'>{t('prejoin.errorMissingName')}</div>}
<div className = 'prejoin-preview-dropdown-container'>
<Popover
content = { hasExtraJoinButtons && <div className = 'prejoin-preview-dropdown-btns'>
{extraButtonsToRender.map(({ key, ...rest }) => (
<Button
disabled = { joiningInProgress }
fullWidth = { true }
key = { key }
type = { BUTTON_TYPES.SECONDARY }
{ ...rest } />
))}
</div> }
onPopoverClose = { _onDropdownClose }
position = 'bottom'
trigger = 'click'
visible = { showJoinByPhoneButtons }>
<ActionButton
OptionsIcon = { showJoinByPhoneButtons ? IconArrowUp : IconArrowDown }
ariaDropDownLabel = { t('prejoin.joinWithoutAudio') }
ariaLabel = { t('prejoin.joinMeeting') }
ariaPressed = { showJoinByPhoneButtons }
disabled = { joiningInProgress }
hasOptions = { hasExtraJoinButtons }
onClick = { _onJoinButtonClick }
onOptionsClick = { _onOptionsClick }
role = 'button'
tabIndex = { 0 }
testId = 'prejoin.joinMeeting'
type = 'primary'>
{ t('prejoin.joinMeeting') }
</ActionButton>
</Popover>
const extraJoinButtons = getExtraJoinButtons();
let extraButtonsToRender = Object.values(extraJoinButtons).filter((val: any) =>
!(prejoinConfig?.hideExtraJoinButtons || []).includes(val.key)
);
if (!hasJoinByPhoneButton) {
extraButtonsToRender = extraButtonsToRender.filter((btn: any) => btn.key !== 'by-phone');
}
const hasExtraJoinButtons = Boolean(extraButtonsToRender.length);
return (
<PreMeetingScreen
showDeviceStatus = { deviceStatusVisible }
title = { t('prejoin.joinMeeting') }
videoMuted = { !showCameraPreview }
videoTrack = { videoTrack }>
<div
className = { classes.inputContainer }
data-testid = 'prejoin.screen'>
{showDisplayNameField.current ? (<Input
autoComplete = { 'name' }
autoFocus = { true }
className = { classes.input }
error = { showErrorOnJoin }
onChange = { setName }
onKeyPress = { onInputKeyPress }
placeholder = { t('dialog.enterDisplayName') }
readOnly = { readOnlyName }
value = { name } />
) : (
<div className = { classes.avatarContainer }>
<Avatar
className = { classes.avatar }
displayName = { name }
participantId = { participantId }
size = { 72 } />
<div className = { classes.avatarName }>{name}</div>
</div>
</div>
{ showDialog && (
<JoinByPhoneDialog // @ts-ignore
joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
onClose = { _closeDialog } />
)}
</PreMeetingScreen>
);
}
}
{showErrorOnJoin && <div
className = { classes.error }
data-testid = 'prejoin.errorMessage'>{t('prejoin.errorMissingName')}</div>}
<div className = { classes.dropdownContainer }>
<Popover
content = { hasExtraJoinButtons && <div className = { classes.dropdownButtons }>
{extraButtonsToRender.map(({ key, ...rest }) => (
<Button
disabled = { joiningInProgress }
fullWidth = { true }
key = { key }
type = { BUTTON_TYPES.SECONDARY }
{ ...rest } />
))}
</div> }
onPopoverClose = { onDropdownClose }
position = 'bottom'
trigger = 'click'
visible = { showJoinByPhoneButtons }>
<ActionButton
OptionsIcon = { showJoinByPhoneButtons ? IconArrowUp : IconArrowDown }
ariaDropDownLabel = { t('prejoin.joinWithoutAudio') }
ariaLabel = { t('prejoin.joinMeeting') }
ariaPressed = { showJoinByPhoneButtons }
disabled = { joiningInProgress }
hasOptions = { hasExtraJoinButtons }
onClick = { onJoinButtonClick }
onOptionsClick = { onOptionsClick }
role = 'button'
tabIndex = { 0 }
testId = 'prejoin.joinMeeting'
type = 'primary'>
{t('prejoin.joinMeeting')}
</ActionButton>
</Popover>
</div>
</div>
{showDialog && (
<JoinByPhoneDialog
joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
onClose = { closeDialog } />
)}
</PreMeetingScreen>
);
};
/**
* Maps (parts of) the redux state to the React {@code Component} props.
@ -450,4 +463,4 @@ const mapDispatchToProps = {
updateSettings
};
export default connect(mapStateToProps, mapDispatchToProps)(translate(Prejoin));
export default connect(mapStateToProps, mapDispatchToProps)(Prejoin);

@ -216,9 +216,10 @@ class JoinByPhoneDialog extends PureComponent<IProps, State> {
* Maps (parts of) the redux state to the React {@code Component} props.
*
* @param {Object} state - The redux state.
* @param {Object} _ownProps - Component's own props.
* @returns {Object}
*/
function mapStateToProps(state: IReduxState) {
function mapStateToProps(state: IReduxState, _ownProps: any) {
return {
dialInNumber: getDefaultDialInNumber(state),
dialOutNumber: getFullDialOutNumber(state),

Loading…
Cancel
Save