ref(feedback-dialog) Update design (#12926)

Convert file to TS
Move styles from SCSS to JSS
This commit is contained in:
Robert Pintilii 2023-03-08 12:46:10 +02:00 committed by GitHub
parent aa57309057
commit c8f1690057
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 124 additions and 123 deletions

View File

@ -44,61 +44,3 @@
-webkit-animation-timing-function: ease-in-out; -webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out animation-timing-function: ease-in-out
} }
.feedback-dialog {
margin-bottom: 5px;
.details {
textarea {
min-height: 100px;
}
}
.input-control {
background-color: $feedbackInputBg;
color: $feedbackInputTextColor;
&::-webkit-input-placeholder {
color: $feedbackInputPlaceholderColor;
}
&::-moz-placeholder { /* Firefox 19+ */
color: $feedbackInputPlaceholderColor;
}
&:-ms-input-placeholder {
color: $feedbackInputPlaceholderColor;
}
}
.rating {
line-height: 1.2;
margin-top: 10px;
text-align: center;
.star-label {
font-size: 14px;
height: 16px;
}
.star-btn {
color: inherit;
cursor: pointer;
display: inline-block;
font-size: 34px;
outline: none;
position: relative;
text-decoration: none;
@include transition(all .2s ease);
&.active,
&:hover,
&.starHover {
color: #36B37E;
};
}
.star-btn:focus,
.star-btn:active {
outline: 1px solid #B8C7E0;
}
}
}

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.7158 3.03843C12.4964 2.33696 11.5037 2.33696 11.2842 3.03843L9.54263 8.60636C9.44381 8.92229 9.14957 9.13606 8.81858 9.13242L2.98497 9.0682C2.25003 9.06011 1.94325 10.0043 2.54258 10.4297L7.29982 13.8067C7.56974 13.9983 7.68213 14.3442 7.57638 14.6579L5.71262 20.1861C5.47782 20.8826 6.28099 21.4661 6.87081 21.0276L11.5525 17.5467C11.8182 17.3492 12.1819 17.3492 12.4475 17.5467L17.1293 21.0276C17.7191 21.4661 18.5223 20.8826 18.2875 20.1861L16.4237 14.6579C16.3179 14.3442 16.4303 13.9983 16.7003 13.8067L21.4575 10.4297C22.0568 10.0043 21.75 9.06011 21.0151 9.0682L15.1815 9.13242C14.8505 9.13606 14.5563 8.92228 14.4574 8.60636L12.7158 3.03843Z" />
</svg>

After

Width:  |  Height:  |  Size: 814 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 5.77465L10.9742 9.05416C10.6778 10.0019 9.79505 10.6433 8.80206 10.6323L5.36607 10.5945L8.16808 12.5835C8.97785 13.1583 9.31502 14.1961 8.99778 15.1371L7.90002 18.3932L10.6576 16.343C11.4545 15.7505 12.5456 15.7505 13.3425 16.343L16.1001 18.3932L15.0023 15.1371C14.6851 14.1961 15.0222 13.1583 15.832 12.5835L18.634 10.5945L15.198 10.6323C14.205 10.6433 13.3223 10.0019 13.0258 9.05416L12 5.77465ZM12.7158 3.03843C12.4964 2.33696 11.5037 2.33696 11.2842 3.03843L9.54263 8.60636C9.44381 8.92229 9.14957 9.13606 8.81858 9.13242L2.98497 9.0682C2.25003 9.06011 1.94325 10.0043 2.54258 10.4297L7.29982 13.8067C7.56974 13.9983 7.68213 14.3442 7.57638 14.6579L5.71262 20.1861C5.47782 20.8826 6.28099 21.4661 6.87081 21.0276L11.5525 17.5467C11.8182 17.3492 12.1819 17.3492 12.4475 17.5467L17.1293 21.0276C17.7191 21.4661 18.5223 20.8826 18.2875 20.1861L16.4237 14.6579C16.3179 14.3442 16.4303 13.9983 16.7003 13.8067L21.4575 10.4297C22.0568 10.0043 21.75 9.06011 21.0151 9.0682L15.1815 9.13242C14.8505 9.13606 14.5563 8.92228 14.4574 8.60636L12.7158 3.03843Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -39,6 +39,8 @@ export { default as IconExclamationSolid } from './exclamation-solid.svg';
export { default as IconExclamationTriangle } from './exclamation-triangle.svg'; export { default as IconExclamationTriangle } from './exclamation-triangle.svg';
export { default as IconExitFullscreen } from './exit-fullscreen.svg'; export { default as IconExitFullscreen } from './exit-fullscreen.svg';
export { default as IconFaceSmile } from './face-smile.svg'; export { default as IconFaceSmile } from './face-smile.svg';
export { default as IconFavorite } from './favorite.svg';
export { default as IconFavoriteSolid } from './favorite-solid.svg';
export { default as IconFeedback } from './feedback.svg'; export { default as IconFeedback } from './feedback.svg';
export { default as IconGear } from './gear.svg'; export { default as IconGear } from './gear.svg';
export { default as IconGoogle } from './google.svg'; export { default as IconGoogle } from './google.svg';

View File

@ -1,23 +1,74 @@
// @flow import { Theme } from '@mui/material';
import { ClassNameMap, withStyles } from '@mui/styles';
import StarIcon from '@atlaskit/icon/glyph/star';
import StarFilledIcon from '@atlaskit/icon/glyph/star-filled';
import React, { Component } from 'react'; import React, { Component } from 'react';
import type { Dispatch } from 'redux'; import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { import { createFeedbackOpenEvent } from '../../analytics/AnalyticsEvents';
createFeedbackOpenEvent, import { sendAnalytics } from '../../analytics/functions';
sendAnalytics import { IReduxState, IStore } from '../../app/types';
} from '../../analytics'; import { IJitsiConference } from '../../base/conference/reducer';
import { isMobileBrowser } from '../../base/environment/utils'; import { isMobileBrowser } from '../../base/environment/utils';
import { translate } from '../../base/i18n'; import { translate } from '../../base/i18n/functions';
import { connect } from '../../base/redux'; import Icon from '../../base/icons/components/Icon';
import { IconFavorite, IconFavoriteSolid } from '../../base/icons/svg';
import { withPixelLineHeight } from '../../base/styles/functions.web';
import Dialog from '../../base/ui/components/web/Dialog'; import Dialog from '../../base/ui/components/web/Dialog';
import Input from '../../base/ui/components/web/Input'; import Input from '../../base/ui/components/web/Input';
import { cancelFeedback, submitFeedback } from '../actions'; import { cancelFeedback, submitFeedback } from '../actions';
declare var APP: Object; const styles = (theme: Theme) => {
declare var interfaceConfig: Object; return {
dialog: {
marginBottom: theme.spacing(1)
},
rating: {
display: 'flex',
flexDirection: 'column' as const,
alignItems: 'center',
justifyContent: 'center',
marginTop: theme.spacing(4),
marginBottom: theme.spacing(3)
},
ratingLabel: {
...withPixelLineHeight(theme.typography.bodyShortBold),
color: theme.palette.text01,
marginBottom: theme.spacing(2),
height: '20px'
},
stars: {
display: 'flex'
},
starBtn: {
display: 'inline-block',
cursor: 'pointer',
marginRight: theme.spacing(3),
'&:last-of-type': {
marginRight: 0
},
'&.active svg': {
fill: theme.palette.success01
},
'&:focus': {
outline: `1px solid ${theme.palette.action01}`,
borderRadius: '4px'
}
},
details: {
'& textarea': {
minHeight: '122px'
}
}
};
};
const scoreAnimationClass const scoreAnimationClass
= interfaceConfig.ENABLE_FEEDBACK_ANIMATION ? 'shake-rotate' : ''; = interfaceConfig.ENABLE_FEEDBACK_ANIMATION ? 'shake-rotate' : '';
@ -34,49 +85,51 @@ const SCORES = [
'feedback.veryGood' 'feedback.veryGood'
]; ];
const ICON_SIZE = 32;
type Scrollable = { type Scrollable = {
scroll: Function scroll: Function;
} };
/** /**
* The type of the React {@code Component} props of {@link FeedbackDialog}. * The type of the React {@code Component} props of {@link FeedbackDialog}.
*/ */
type Props = { interface IProps extends WithTranslation {
/** /**
* The cached feedback message, if any, that was set when closing a previous * The cached feedback message, if any, that was set when closing a previous
* instance of {@code FeedbackDialog}. * instance of {@code FeedbackDialog}.
*/ */
_message: string, _message: string;
/** /**
* The cached feedback score, if any, that was set when closing a previous * The cached feedback score, if any, that was set when closing a previous
* instance of {@code FeedbackDialog}. * instance of {@code FeedbackDialog}.
*/ */
_score: number, _score: number;
/**
* An object containing the CSS classes.
*/
classes: ClassNameMap<string>;
/** /**
* The JitsiConference that is being rated. The conference is passed in * The JitsiConference that is being rated. The conference is passed in
* because feedback can occur after a conference has been left, so * because feedback can occur after a conference has been left, so
* references to it may no longer exist in redux. * references to it may no longer exist in redux.
*/ */
conference: Object, conference: IJitsiConference;
/** /**
* Invoked to signal feedback submission or canceling. * Invoked to signal feedback submission or canceling.
*/ */
dispatch: Dispatch<any>, dispatch: IStore['dispatch'];
/** /**
* Callback invoked when {@code FeedbackDialog} is unmounted. * Callback invoked when {@code FeedbackDialog} is unmounted.
*/ */
onClose: Function, onClose: Function;
}
/**
* Invoked to obtain translated strings.
*/
t: Function
};
/** /**
* The type of the React {@code Component} state of {@link FeedbackDialog}. * The type of the React {@code Component} state of {@link FeedbackDialog}.
@ -86,20 +139,20 @@ type State = {
/** /**
* The currently entered feedback message. * The currently entered feedback message.
*/ */
message: string, message: string;
/** /**
* The score selection index which is currently being hovered. The value -1 * The score selection index which is currently being hovered. The value -1
* is used as a sentinel value to match store behavior of using -1 for no * is used as a sentinel value to match store behavior of using -1 for no
* score having been selected. * score having been selected.
*/ */
mousedOverScore: number, mousedOverScore: number;
/** /**
* The currently selected score selection index. The score will not be 0 * The currently selected score selection index. The score will not be 0
* indexed so subtract one to map with SCORES. * indexed so subtract one to map with SCORES.
*/ */
score: number score: number;
}; };
/** /**
@ -109,13 +162,19 @@ type State = {
* *
* @augments Component * @augments Component
*/ */
class FeedbackDialog extends Component<Props, State> { class FeedbackDialog extends Component<IProps, State> {
/** /**
* An array of objects with click handlers for each of the scores listed in * An array of objects with click handlers for each of the scores listed in
* the constant SCORES. This pattern is used for binding event handlers only * the constant SCORES. This pattern is used for binding event handlers only
* once for each score selection icon. * once for each score selection icon.
*/ */
_scoreClickConfigurations: Array<Object>; _scoreClickConfigurations: Array<{
_onClick: (e: React.MouseEvent) => void;
_onKeyDown: (e: React.KeyboardEvent) => void;
_onMouseOver: (e: React.MouseEvent) => void;
}>;
_onScrollTop: (node: Scrollable | null) => void;
/** /**
* Initializes a new {@code FeedbackDialog} instance. * Initializes a new {@code FeedbackDialog} instance.
@ -123,7 +182,7 @@ class FeedbackDialog extends Component<Props, State> {
* @param {Object} props - The read-only React {@code Component} props with * @param {Object} props - The read-only React {@code Component} props with
* which the new instance is to be initialized. * which the new instance is to be initialized.
*/ */
constructor(props: Props) { constructor(props: IProps) {
super(props); super(props);
const { _message, _score } = this.props; const { _message, _score } = this.props;
@ -157,8 +216,9 @@ class FeedbackDialog extends Component<Props, State> {
this._scoreClickConfigurations = SCORES.map((textKey, index) => { this._scoreClickConfigurations = SCORES.map((textKey, index) => {
return { return {
_onClick: () => this._onScoreSelect(index), _onClick: () => this._onScoreSelect(index),
_onKeyPres: e => { _onKeyDown: (e: React.KeyboardEvent) => {
if (e.key === ' ' || e.key === 'Enter') { if (e.key === ' ' || e.key === 'Enter') {
e.stopPropagation();
e.preventDefault(); e.preventDefault();
this._onScoreSelect(index); this._onScoreSelect(index);
} }
@ -176,8 +236,8 @@ class FeedbackDialog extends Component<Props, State> {
// On some mobile browsers opening Feedback dialog scrolls down the whole content because of the keyboard. // On some mobile browsers opening Feedback dialog scrolls down the whole content because of the keyboard.
// By scrolling to the top we prevent hiding the feedback stars so the user knows those exist. // By scrolling to the top we prevent hiding the feedback stars so the user knows those exist.
this._onScrollTop = (node: ?Scrollable) => { this._onScrollTop = (node: Scrollable | null) => {
node && node.scroll && node.scroll(0, 0); node?.scroll?.(0, 0);
}; };
} }
@ -215,14 +275,14 @@ class FeedbackDialog extends Component<Props, State> {
const scoreToDisplayAsSelected const scoreToDisplayAsSelected
= mousedOverScore > -1 ? mousedOverScore : score; = mousedOverScore > -1 ? mousedOverScore : score;
const { t } = this.props; const { classes, t } = this.props;
const scoreIcons = this._scoreClickConfigurations.map( const scoreIcons = this._scoreClickConfigurations.map(
(config, index) => { (config, index) => {
const isFilled = index <= scoreToDisplayAsSelected; const isFilled = index <= scoreToDisplayAsSelected;
const activeClass = isFilled ? 'active' : ''; const activeClass = isFilled ? 'active' : '';
const className const className
= `star-btn ${scoreAnimationClass} ${activeClass}`; = `${classes.starBtn} ${scoreAnimationClass} ${activeClass}`;
return ( return (
<span <span
@ -230,19 +290,19 @@ class FeedbackDialog extends Component<Props, State> {
className = { className } className = { className }
key = { index } key = { index }
onClick = { config._onClick } onClick = { config._onClick }
onKeyPress = { config._onKeyPres } onKeyDown = { config._onKeyDown }
role = 'button' role = 'button'
tabIndex = { 0 } tabIndex = { 0 }
{ ...(isMobileBrowser() ? {} : { { ...(isMobileBrowser() ? {} : {
onMouseOver: config._onMouseOver onMouseOver: config._onMouseOver
}) }> }) }>
{ isFilled { isFilled
? <StarFilledIcon ? <Icon
label = 'star-filled' size = { ICON_SIZE }
size = 'xlarge' /> src = { IconFavoriteSolid } />
: <StarIcon : <Icon
label = 'star' size = { ICON_SIZE }
size = 'xlarge' /> } src = { IconFavorite } /> }
</span> </span>
); );
}); });
@ -255,23 +315,24 @@ class FeedbackDialog extends Component<Props, State> {
}} }}
onCancel = { this._onCancel } onCancel = { this._onCancel }
onSubmit = { this._onSubmit } onSubmit = { this._onSubmit }
size = 'large'
titleKey = 'feedback.rateExperience'> titleKey = 'feedback.rateExperience'>
<div className = 'feedback-dialog'> <div className = { classes.dialog }>
<div className = 'rating'> <div className = { classes.rating }>
<div <div
aria-label = { this.props.t('feedback.star') } aria-label = { this.props.t('feedback.star') }
className = 'star-label' > className = { classes.ratingLabel } >
<p id = 'starLabel'> <p id = 'starLabel'>
{ t(SCORES[scoreToDisplayAsSelected]) } { t(SCORES[scoreToDisplayAsSelected]) }
</p> </p>
</div> </div>
<div <div
className = 'stars' className = { classes.stars }
onMouseLeave = { this._onScoreContainerMouseLeave }> onMouseLeave = { this._onScoreContainerMouseLeave }>
{ scoreIcons } { scoreIcons }
</div> </div>
</div> </div>
<div className = 'details'> <div className = { classes.details }>
<Input <Input
autoFocus = { true } autoFocus = { true }
id = 'feedbackTextArea' id = 'feedbackTextArea'
@ -285,8 +346,6 @@ class FeedbackDialog extends Component<Props, State> {
); );
} }
_onCancel: () => boolean;
/** /**
* Dispatches an action notifying feedback was not submitted. The submitted * Dispatches an action notifying feedback was not submitted. The submitted
* score will have one added as the rest of the app does not expect 0 * score will have one added as the rest of the app does not expect 0
@ -304,8 +363,6 @@ class FeedbackDialog extends Component<Props, State> {
return true; return true;
} }
_onMessageChange: (Object) => void;
/** /**
* Updates the known entered feedback message. * Updates the known entered feedback message.
* *
@ -314,7 +371,7 @@ class FeedbackDialog extends Component<Props, State> {
* @private * @private
* @returns {void} * @returns {void}
*/ */
_onMessageChange(newValue) { _onMessageChange(newValue: string) {
this.setState({ message: newValue }); this.setState({ message: newValue });
} }
@ -325,12 +382,10 @@ class FeedbackDialog extends Component<Props, State> {
* @private * @private
* @returns {void} * @returns {void}
*/ */
_onScoreSelect(score) { _onScoreSelect(score: number) {
this.setState({ score }); this.setState({ score });
} }
_onScoreContainerMouseLeave: () => void;
/** /**
* Sets the currently hovered score to null to indicate no hover is * Sets the currently hovered score to null to indicate no hover is
* occurring. * occurring.
@ -350,12 +405,10 @@ class FeedbackDialog extends Component<Props, State> {
* @private * @private
* @returns {void} * @returns {void}
*/ */
_onScoreMouseOver(mousedOverScore) { _onScoreMouseOver(mousedOverScore: number) {
this.setState({ mousedOverScore }); this.setState({ mousedOverScore });
} }
_onSubmit: () => void;
/** /**
* Dispatches the entered feedback for submission. The submitted score will * Dispatches the entered feedback for submission. The submitted score will
* have one added as the rest of the app does not expect 0 indexing. * have one added as the rest of the app does not expect 0 indexing.
@ -373,8 +426,6 @@ class FeedbackDialog extends Component<Props, State> {
return true; return true;
} }
_onScrollTop: (node: ?Scrollable) => void;
} }
/** /**
@ -386,7 +437,7 @@ class FeedbackDialog extends Component<Props, State> {
* @returns {{ * @returns {{
* }} * }}
*/ */
function _mapStateToProps(state) { function _mapStateToProps(state: IReduxState) {
const { message, score } = state['features/feedback']; const { message, score } = state['features/feedback'];
return { return {
@ -407,4 +458,4 @@ function _mapStateToProps(state) {
}; };
} }
export default translate(connect(_mapStateToProps)(FeedbackDialog)); export default withStyles(styles)(translate(connect(_mapStateToProps)(FeedbackDialog)));