feat(prejoin) Update design (#12844)

pull/12846/head jitsi-meet_8260
Robert Pintilii 2 years ago committed by GitHub
parent 992bf47850
commit 0d5dae7ab9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      css/premeeting/_prejoin.scss
  2. 137
      css/premeeting/_premeeting-screens.scss
  3. 11
      react/features/base/premeeting/components/web/ConnectionStatus.tsx
  4. 78
      react/features/base/premeeting/components/web/PreMeetingScreen.tsx
  5. 2
      react/features/base/ui/Tokens.ts
  6. 4
      react/features/base/ui/components/web/Input.tsx
  7. 10
      react/features/base/ui/constants.web.ts
  8. 172
      react/features/prejoin/components/web/Prejoin.tsx
  9. 50
      react/features/prejoin/components/web/preview/DeviceStatus.tsx

@ -41,11 +41,11 @@
&-dropdown-btns {
padding: 8px 0;
}
&-dropdown-container {
position: relative;
width: 100%;
/**
* Override default InlineDialog behaviour, since it does not play nicely with relative widths
*/
@ -56,5 +56,12 @@
width: 100%;
}
}
}
}
.prejoin-input {
margin-bottom: 16px;
& input {
text-align: center;
}
}

@ -1,14 +1,4 @@
.premeeting-screen {
background: #292929;
bottom: 0;
display: flex;
font-size: 1.3em;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: $toolbarZ + 2;
.premeeting-screen {
.action-btn {
border-radius: 6px;
box-sizing: border-box;
@ -75,129 +65,38 @@
}
}
.content {
align-items: center;
box-sizing: border-box;
display: flex;
flex-direction: column;
flex-shrink: 0;
height: 100%;
margin: 0 30px;
padding: 24px 0 16px;
#new-toolbox {
bottom: 0;
position: relative;
width: $prejoinDefaultContentWidth;
z-index: $toolbarZ + 2;
&-controls {
align-items: center;
display: flex;
flex-direction: column;
margin: auto;
width: 100%;
.title {
color: #fff;
font-size: 28px;
font-weight: 600;
letter-spacing: -0.015;
line-height: 36px;
margin-bottom: 16px;
text-align: center;
}
input.field {
background-color: white;
border: none;
outline: none;
border-radius: 6px;
font-size: 14px;
line-height: 20px;
margin-bottom: 16px;
color: #1C2025;
padding: 10px 16px;
text-align: center;
width: 100%;
&.error {
border: 1px solid #E04757;
}
&.focused {
box-shadow: 0px 0px 1px 1.5px black, 0px 0px 1.3px 4px white;
}
}
#new-toolbox {
bottom: 0;
position: relative;
transition: none;
.toolbox-content {
margin-bottom: 4px;
}
.toolbox-content-items {
@include ltr;
background: transparent;
box-shadow: none;
display: flex;
justify-content: space-evenly;
padding: 8px 0;
}
transition: none;
.toolbox-content,
.toolbox-content-wrapper,
.toolbox-content-items {
box-sizing: border-box;
width: 100%;
}
}
.toolbox-content {
margin-bottom: 4px;
}
}
@media (max-width: 720px) {
flex-direction: column-reverse;
.content {
height: auto;
margin: 0 auto;
.toolbox-content-items {
@include ltr;
background: transparent;
box-shadow: none;
display: flex;
justify-content: space-between;
padding: 8px 0;
}
}
// mobile phone landscape
@media (max-height: 420px) {
div.content {
padding: 16px 16px 0 16px;
.toolbox-content,
.toolbox-content-wrapper,
.toolbox-content-items {
box-sizing: border-box;
width: 100%;
}
}
@media (max-width: 400px) {
.content {
padding: 16px;
width: 100%;
&-controls {
input.field {
font-size: 16px;
padding: 14px 16px;
}
}
.title {
display: none;
}
}
.device-status-error {
border-radius: 0;
margin: 0 -16px;
}
input.field {
font-size: 16px;
padding: 14px 16px;
}
.action-btn {
font-size: 16px;
margin-bottom: 8px;

@ -7,6 +7,7 @@ import { translate } from '../../../i18n/functions';
import Icon from '../../../icons/components/Icon';
import { IconArrowDown, IconWifi1Bar, IconWifi2Bars, IconWifi3Bars } from '../../../icons/svg';
import { connect } from '../../../redux/functions';
import { withPixelLineHeight } from '../../../styles/functions.web';
import { PREJOIN_DEFAULT_CONTENT_WIDTH } from '../../../ui/components/variables';
import { CONNECTION_TYPE } from '../../constants';
import { getConnectionData } from '../../functions';
@ -27,11 +28,8 @@ interface IProps extends WithTranslation {
const useStyles = makeStyles()(theme => {
return {
connectionStatus: {
borderRadius: '6px',
color: '#fff',
fontSize: '12px',
letterSpacing: '0.16px',
lineHeight: '16px',
...withPixelLineHeight(theme.typography.bodyShortRegular),
position: 'absolute',
width: '100%',
@ -56,14 +54,15 @@ const useStyles = makeStyles()(theme => {
backgroundColor: 'rgba(0, 0, 0, 0.7)',
alignItems: 'center',
display: 'flex',
padding: '14px 16px'
padding: '12px 16px',
borderRadius: theme.shape.borderRadius
},
'& .con-status-circle': {
borderRadius: '50%',
display: 'inline-block',
padding: theme.spacing(1),
marginRight: theme.spacing(3)
marginRight: theme.spacing(2)
},
'& .con-status--good': {

@ -1,5 +1,7 @@
/* eslint-disable lines-around-comment */
import clsx from 'clsx';
import React, { ReactNode } from 'react';
import { connect } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../../app/types';
@ -9,12 +11,12 @@ import { Toolbox } from '../../../../toolbox/components/web';
import { getConferenceName } from '../../../conference/functions';
import { PREMEETING_BUTTONS, THIRD_PARTY_PREJOIN_BUTTONS } from '../../../config/constants';
import { getToolbarButtons, isToolbarButtonEnabled } from '../../../config/functions.web';
import { connect } from '../../../redux/functions';
import { withPixelLineHeight } from '../../../styles/functions.web';
import ConnectionStatus from './ConnectionStatus';
// @ts-ignore
import Preview from './Preview';
/* eslint-enable lines-around-comment */
interface IProps {
@ -51,7 +53,7 @@ interface IProps {
/**
* Indicates whether the copy url button should be shown.
*/
showCopyUrlButton: boolean;
showCopyUrlButton?: boolean;
/**
* Indicates whether the device status should be shown.
@ -86,7 +88,64 @@ interface IProps {
const useStyles = makeStyles()(theme => {
return {
subtitle: {
container: {
height: '100%',
position: 'absolute',
inset: '0 0 0 0',
display: 'flex',
backgroundColor: theme.palette.ui01,
zIndex: 252,
'@media (max-width: 720px)': {
flexDirection: 'column-reverse'
}
},
content: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
flexShrink: 0,
boxSizing: 'border-box',
margin: '0 48px',
padding: '24px 0 16px',
position: 'relative',
width: '300px',
height: '100%',
zIndex: 252,
'@media (max-width: 720px)': {
height: 'auto',
margin: '0 auto'
},
// mobile phone landscape
'@media (max-width: 420px)': {
padding: '16px 16px 0 16px',
width: '100%'
},
'@media (max-width: 400px)': {
padding: '16px'
}
},
contentControls: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
margin: 'auto',
width: '100%'
},
title: {
...withPixelLineHeight(theme.typography.heading4),
color: `${theme.palette.text01}!important`,
marginBottom: theme.spacing(3),
textAlign: 'center',
'@media (max-width: 400px)': {
display: 'none'
}
},
roomName: {
...withPixelLineHeight(theme.typography.heading5),
color: theme.palette.text01,
marginBottom: theme.spacing(4),
@ -112,7 +171,6 @@ const PreMeetingScreen = ({
videoTrack
}: IProps) => {
const { classes } = useStyles();
const containerClassName = `premeeting-screen ${className ? className : ''}`;
const style = _premeetingBackground ? {
background: _premeetingBackground,
backgroundPosition: 'center',
@ -120,17 +178,17 @@ const PreMeetingScreen = ({
} : {};
return (
<div className = { containerClassName }>
<div className = { clsx('premeeting-screen', classes.container, className) }>
<div style = { style }>
<div className = 'content'>
<div className = { classes.content }>
<ConnectionStatus />
<div className = 'content-controls'>
<h1 className = 'title'>
<div className = { classes.contentControls }>
<h1 className = { classes.title }>
{title}
</h1>
{_roomName && (
<span className = { classes.subtitle }>
<span className = { classes.roomName }>
{_roomName}
</span>
)}
@ -175,7 +233,7 @@ function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
? premeetingButtons
: premeetingButtons.filter(b => isToolbarButtonEnabled(b, toolbarButtons)),
_premeetingBackground: premeetingBackground,
_roomName: hideConferenceSubject ? undefined : getConferenceName(state)
_roomName: (hideConferenceSubject ? undefined : getConferenceName(state)) ?? ''
};
}

@ -48,7 +48,6 @@ export const colors = {
// after we replace them in the components.
primary10: '#17A0DB',
primary11: '#1081B2',
primary12: '#B8C7E0',
surface00: '#111111',
surface12: '#AAAAAA',
surface13: '#495258',
@ -199,7 +198,6 @@ export const colorMap = {
border01: 'surface08',
border02: 'surface06',
border03: 'surface04',
border04: 'primary12',
border05: 'surface07',
borderError: 'error06',
warning03: 'warning07',

@ -10,6 +10,7 @@ import { IInputProps } from '../types';
interface IProps extends IInputProps {
accessibilityLabel?: string;
autoComplete?: string;
autoFocus?: boolean;
bottomLabel?: string;
className?: string;
@ -131,6 +132,7 @@ const useStyles = makeStyles()(theme => {
const Input = React.forwardRef<any, IProps>(({
accessibilityLabel,
autoComplete,
autoFocus,
bottomLabel,
className,
@ -175,6 +177,7 @@ const Input = React.forwardRef<any, IProps>(({
{textarea ? (
<TextareaAutosize
aria-label = { accessibilityLabel }
autoComplete = { autoComplete }
autoFocus = { autoFocus }
className = { cx(styles.input, isMobile && 'is-mobile',
error && 'error', clearable && styles.clearableInput, icon && 'icon-input') }
@ -194,6 +197,7 @@ const Input = React.forwardRef<any, IProps>(({
) : (
<input
aria-label = { accessibilityLabel }
autoComplete = { autoComplete }
autoFocus = { autoFocus }
className = { cx(styles.input, isMobile && 'is-mobile',
error && 'error', clearable && styles.clearableInput, icon && 'icon-input') }

@ -265,15 +265,7 @@ export const commonStyles = (theme: Theme) => {
padding: 6,
textAlign: 'center' as const,
pointerEvents: 'all' as const,
boxShadow: '0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15)',
'& > div': {
marginLeft: 8,
'&:first-child': {
marginLeft: 0
}
}
boxShadow: '0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15)'
}
};
};

@ -1,25 +1,30 @@
// @flow
import InlineDialog from '@atlaskit/inline-dialog';
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { IReduxState } from '../../../app/types';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { Avatar } from '../../../base/avatar';
import { isNameReadOnly } from '../../../base/config';
import { translate } from '../../../base/i18n';
import { IconArrowDown, IconArrowUp, IconPhoneRinging, IconVolumeOff } from '../../../base/icons';
import { isVideoMutedByUser } from '../../../base/media';
import { getLocalParticipant } from '../../../base/participants';
import { ActionButton, InputField, PreMeetingScreen } from '../../../base/premeeting';
import { connect } from '../../../base/redux';
import { getDisplayName, updateSettings } from '../../../base/settings';
import { getLocalJitsiVideoTrack } from '../../../base/tracks';
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';
import ActionButton from '../../../base/premeeting/components/web/ActionButton';
import PreMeetingScreen from '../../../base/premeeting/components/web/PreMeetingScreen';
import { connect } from '../../../base/redux/functions';
import { updateSettings } from '../../../base/settings/actions';
import { getDisplayName } from '../../../base/settings/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';
import { BUTTON_TYPES } from '../../../base/ui/constants.any';
import {
joinConference as joinConferenceAction,
joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
} from '../../actions';
} from '../../actions.web';
import {
isDeviceStatusVisible,
isDisplayNameRequired,
@ -28,114 +33,112 @@ import {
isPrejoinDisplayNameVisible
} from '../../functions';
// @ts-ignore
import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
type Props = {
interface IProps extends WithTranslation {
/**
* Indicates whether the display name is editable.
*/
canEditDisplayName: boolean,
canEditDisplayName: boolean;
/**
* Flag signaling if the device status is visible or not.
*/
deviceStatusVisible: boolean,
deviceStatusVisible: boolean;
/**
* If join by phone button should be visible.
*/
hasJoinByPhoneButton: boolean,
hasJoinByPhoneButton: boolean;
/**
* Joins the current meeting.
*/
joinConference: Function,
joinConference: Function;
/**
* Joins the current meeting without audio.
*/
joinConferenceWithoutAudio: Function,
joinConferenceWithoutAudio: Function;
/**
* Whether conference join is in progress.
*/
joiningInProgress: boolean,
joiningInProgress: boolean;
/**
* The name of the user that is about to join.
*/
name: string,
/**
* Updates settings.
*/
updateSettings: Function,
name: string;
/**
* Local participant id.
*/
participantId: string,
participantId: string;
/**
* The prejoin config.
*/
prejoinConfig?: Object,
prejoinConfig?: any;
/**
* Whether the name input should be read only or not.
*/
readOnlyName: boolean,
readOnlyName: boolean;
/**
* Sets visibility of the 'JoinByPhoneDialog'.
*/
setJoinByPhoneDialogVisiblity: Function,
setJoinByPhoneDialogVisiblity: Function;
/**
* Flag signaling the visibility of camera preview.
*/
showCameraPreview: boolean,
showCameraPreview: boolean;
/**
* If should show an error when joining without a name.
* If 'JoinByPhoneDialog' is visible or not.
*/
showErrorOnJoin: boolean,
showDialog: boolean;
/**
* If 'JoinByPhoneDialog' is visible or not.
* If should show an error when joining without a name.
*/
showDialog: boolean,
showErrorOnJoin: boolean;
/**
* Used for translation.
* Updates settings.
*/
t: Function,
updateSettings: Function;
/**
* The JitsiLocalTrack to display.
*/
videoTrack: ?Object
};
videoTrack?: Object;
}
type State = {
interface IState {
/**
* Flag controlling the visibility of the 'join by phone' buttons.
*/
showJoinByPhoneButtons: boolean
showJoinByPhoneButtons: boolean;
}
/**
* This component is displayed before joining a meeting.
*/
class Prejoin extends Component<Props, State> {
class Prejoin extends Component<IProps, IState> {
showDisplayNameField: boolean;
/**
* Initializes a new {@code Prejoin} instance.
*
* @inheritdoc
*/
constructor(props) {
constructor(props: IProps) {
super(props);
this.state = {
@ -150,12 +153,11 @@ class Prejoin extends Component<Props, State> {
this._setName = this._setName.bind(this);
this._onJoinConferenceWithoutAudioKeyPress = this._onJoinConferenceWithoutAudioKeyPress.bind(this);
this._showDialogKeyPress = this._showDialogKeyPress.bind(this);
this._onJoinKeyPress = this._onJoinKeyPress.bind(this);
this._getExtraJoinButtons = this._getExtraJoinButtons.bind(this);
this._onInputKeyPress = this._onInputKeyPress.bind(this);
this.showDisplayNameField = props.canEditDisplayName || props.showErrorOnJoin;
}
_onJoinButtonClick: () => void;
/**
* Handler for the join button.
@ -170,24 +172,6 @@ class Prejoin extends Component<Props, State> {
this.props.joinConference();
}
_onJoinKeyPress: (Object) => void;
/**
* KeyPress handler for accessibility.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onJoinKeyPress(e) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
this._onJoinButtonClick();
}
}
_onDropdownClose: () => void;
/**
* Closes the dropdown.
*
@ -199,38 +183,32 @@ class Prejoin extends Component<Props, State> {
});
}
_onOptionsClick: () => void;
/**
* Displays the join by phone buttons dropdown.
*
* @param {Object} e - The synthetic event.
* @returns {void}
*/
_onOptionsClick(e) {
e.stopPropagation();
_onOptionsClick(e?: React.KeyboardEvent | React.MouseEvent | undefined) {
e?.stopPropagation();
this.setState({
showJoinByPhoneButtons: !this.state.showJoinByPhoneButtons
});
}
_setName: () => void;
/**
* Sets the guest participant name.
*
* @param {string} displayName - Participant name.
* @returns {void}
*/
_setName(displayName) {
_setName(displayName: string) {
this.props.updateSettings({
displayName
});
}
_closeDialog: () => void;
/**
* Closes the join by phone dialog.
*
@ -240,8 +218,6 @@ class Prejoin extends Component<Props, State> {
this.props.setJoinByPhoneDialogVisiblity(false);
}
_showDialog: () => void;
/**
* Displays the dialog for joining a meeting by phone.
*
@ -252,8 +228,6 @@ class Prejoin extends Component<Props, State> {
this._onDropdownClose();
}
_showDialogKeyPress: (Object) => void;
/**
* KeyPress handler for accessibility.
*
@ -261,15 +235,13 @@ class Prejoin extends Component<Props, State> {
*
* @returns {void}
*/
_showDialogKeyPress(e) {
_showDialogKeyPress(e: React.KeyboardEvent) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
this._showDialog();
}
}
_onJoinConferenceWithoutAudioKeyPress: (Object) => void;
/**
* KeyPress handler for accessibility.
*
@ -277,7 +249,7 @@ class Prejoin extends Component<Props, State> {
*
* @returns {void}
*/
_onJoinConferenceWithoutAudioKeyPress(e) {
_onJoinConferenceWithoutAudioKeyPress(e: React.KeyboardEvent) {
if (this.props.joinConferenceWithoutAudio
&& (e.key === ' '
|| e.key === 'Enter')) {
@ -286,8 +258,6 @@ class Prejoin extends Component<Props, State> {
}
}
_getExtraJoinButtons: () => Object;
/**
* Gets the list of extra join buttons.
*
@ -320,6 +290,20 @@ class Prejoin extends Component<Props, State> {
};
}
/**
* Handle keypress on input.
*
* @param {KeyboardEvent} e - Keyboard event.
* @returns {void}
*/
_onInputKeyPress(e: React.KeyboardEvent) {
const { joinConference } = this.props;
if (e.key === 'Enter') {
joinConference();
}
}
/**
* Implements React's {@link Component#render()}.
*
@ -330,7 +314,6 @@ class Prejoin extends Component<Props, State> {
const {
deviceStatusVisible,
hasJoinByPhoneButton,
joinConference,
joinConferenceWithoutAudio,
joiningInProgress,
name,
@ -343,16 +326,16 @@ class Prejoin extends Component<Props, State> {
t,
videoTrack
} = this.props;
const { _closeDialog, _onDropdownClose, _onJoinButtonClick, _onJoinKeyPress,
_onOptionsClick, _setName } = this;
const { _closeDialog, _onDropdownClose, _onJoinButtonClick,
_onOptionsClick, _setName, _onInputKeyPress } = this;
const extraJoinButtons = this._getExtraJoinButtons();
let extraButtonsToRender = Object.values(extraJoinButtons).filter((val: Object) =>
let extraButtonsToRender = Object.values(extraJoinButtons).filter((val: any) =>
!(prejoinConfig?.hideExtraJoinButtons || []).includes(val.key)
);
if (!hasJoinByPhoneButton) {
extraButtonsToRender = extraButtonsToRender.filter((btn: Object) => btn.key !== 'by-phone');
extraButtonsToRender = extraButtonsToRender.filter((btn: any) => btn.key !== 'by-phone');
}
const hasExtraJoinButtons = Boolean(extraButtonsToRender.length);
const { showJoinByPhoneButtons } = this.state;
@ -366,14 +349,14 @@ class Prejoin extends Component<Props, State> {
<div
className = 'prejoin-input-area'
data-testid = 'prejoin.screen'>
{this.showDisplayNameField ? (<InputField
{this.showDisplayNameField ? (<Input
autoComplete = { 'name' }
autoFocus = { true }
className = { showErrorOnJoin ? 'error' : '' }
hasError = { showErrorOnJoin }
className = 'prejoin-input'
error = { showErrorOnJoin }
onChange = { _setName }
onSubmit = { joinConference }
placeHolder = { t('dialog.enterDisplayName') }
onKeyPress = { _onInputKeyPress }
placeholder = { t('dialog.enterDisplayName') }
readOnly = { readOnlyName }
value = { name } />
) : (
@ -394,7 +377,7 @@ class Prejoin extends Component<Props, State> {
<div className = 'prejoin-preview-dropdown-container'>
<InlineDialog
content = { hasExtraJoinButtons && <div className = 'prejoin-preview-dropdown-btns'>
{extraButtonsToRender.map(({ key, ...rest }: Object) => (
{extraButtonsToRender.map(({ key, ...rest }) => (
<Button
disabled = { joiningInProgress }
fullWidth = { true }
@ -413,7 +396,6 @@ class Prejoin extends Component<Props, State> {
disabled = { joiningInProgress }
hasOptions = { hasExtraJoinButtons }
onClick = { _onJoinButtonClick }
onKeyPress = { _onJoinKeyPress }
onOptionsClick = { _onOptionsClick }
role = 'button'
tabIndex = { 0 }
@ -440,10 +422,10 @@ class Prejoin extends Component<Props, State> {
* @param {Object} state - The redux state.
* @returns {Object}
*/
function mapStateToProps(state): Object {
function mapStateToProps(state: IReduxState) {
const name = getDisplayName(state);
const showErrorOnJoin = isDisplayNameRequired(state) && !name;
const { id: participantId } = getLocalParticipant(state);
const { id: participantId } = getLocalParticipant(state) ?? {};
const { joiningInProgress } = state['features/prejoin'];
return {

@ -4,9 +4,8 @@ import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../../app/types';
import { translate } from '../../../../base/i18n/functions';
import Icon from '../../../../base/icons/components/Icon';
import { IconCheck, IconExclamationTriangle } from '../../../../base/icons/svg';
import { connect } from '../../../../base/redux/functions';
import { withPixelLineHeight } from '../../../../base/styles/functions.web';
import {
getDeviceStatusText,
getDeviceStatusType
@ -29,13 +28,12 @@ export interface IProps extends WithTranslation {
const useStyles = makeStyles()(theme => {
return {
deviceStatus: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
...withPixelLineHeight(theme.typography.bodyShortRegular),
color: '#fff',
display: 'flex',
fontSize: '14px',
lineHeight: '20px',
padding: '6px',
textAlign: 'center',
marginTop: theme.spacing(4),
'& span': {
marginLeft: theme.spacing(3)
@ -47,33 +45,23 @@ const useStyles = makeStyles()(theme => {
borderRadius: '6px',
color: theme.palette.uiBackground,
padding: '12px 16px',
textAlign: 'left'
},
'& .device-icon': {
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
display: 'inline-block',
height: '16px',
width: '16px'
textAlign: 'left',
marginTop: theme.spacing(2)
},
'& .device-icon--ok svg path': {
fill: '#189B55'
'@media (max-width: 720px)': {
marginTop: 0
}
},
indicator: {
width: '16px',
height: '16px',
borderRadius: '100%',
backgroundColor: theme.palette.success01
}
};
});
const iconMap = {
warning: {
src: IconExclamationTriangle,
className: 'device-icon--warning'
},
ok: {
src: IconCheck,
className: 'device-icon--ok'
}
};
/**
* Strip showing the current status of the devices.
* User is informed if there are missing or malfunctioning devices.
@ -82,7 +70,6 @@ const iconMap = {
*/
function DeviceStatus({ deviceStatusType, deviceStatusText, t }: IProps) {
const { classes, cx } = useStyles();
const { src, className } = iconMap[deviceStatusType as keyof typeof iconMap];
const hasError = deviceStatusType === 'warning';
const containerClassName = cx(classes.deviceStatus, { 'device-status-error': hasError });
@ -91,10 +78,7 @@ function DeviceStatus({ deviceStatusType, deviceStatusText, t }: IProps) {
className = { containerClassName }
role = 'alert'
tabIndex = { -1 }>
<Icon
className = { `device-icon ${className}` }
size = { 16 }
src = { src } />
{!hasError && <div className = { classes.indicator } />}
<span role = 'heading'>
{hasError ? t('prejoin.errorNoPermissions') : t(deviceStatusText ?? '')}
</span>

Loading…
Cancel
Save