ref(feedback-dialog) Update design (#12926)
Convert file to TS Move styles from SCSS to JSS
This commit is contained in:
parent
aa57309057
commit
c8f1690057
|
@ -44,61 +44,3 @@
|
|||
-webkit-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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -39,6 +39,8 @@ export { default as IconExclamationSolid } from './exclamation-solid.svg';
|
|||
export { default as IconExclamationTriangle } from './exclamation-triangle.svg';
|
||||
export { default as IconExitFullscreen } from './exit-fullscreen.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 IconGear } from './gear.svg';
|
||||
export { default as IconGoogle } from './google.svg';
|
||||
|
|
|
@ -1,23 +1,74 @@
|
|||
// @flow
|
||||
|
||||
import StarIcon from '@atlaskit/icon/glyph/star';
|
||||
import StarFilledIcon from '@atlaskit/icon/glyph/star-filled';
|
||||
import { Theme } from '@mui/material';
|
||||
import { ClassNameMap, withStyles } from '@mui/styles';
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
createFeedbackOpenEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { createFeedbackOpenEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { IJitsiConference } from '../../base/conference/reducer';
|
||||
import { isMobileBrowser } from '../../base/environment/utils';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { connect } from '../../base/redux';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
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 Input from '../../base/ui/components/web/Input';
|
||||
import { cancelFeedback, submitFeedback } from '../actions';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
const styles = (theme: Theme) => {
|
||||
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
|
||||
= interfaceConfig.ENABLE_FEEDBACK_ANIMATION ? 'shake-rotate' : '';
|
||||
|
@ -34,49 +85,51 @@ const SCORES = [
|
|||
'feedback.veryGood'
|
||||
];
|
||||
|
||||
const ICON_SIZE = 32;
|
||||
|
||||
type Scrollable = {
|
||||
scroll: Function
|
||||
}
|
||||
scroll: Function;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* instance of {@code FeedbackDialog}.
|
||||
*/
|
||||
_message: string,
|
||||
_message: string;
|
||||
|
||||
/**
|
||||
* The cached feedback score, if any, that was set when closing a previous
|
||||
* 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
|
||||
* because feedback can occur after a conference has been left, so
|
||||
* references to it may no longer exist in redux.
|
||||
*/
|
||||
conference: Object,
|
||||
conference: IJitsiConference;
|
||||
|
||||
/**
|
||||
* Invoked to signal feedback submission or canceling.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Callback invoked when {@code FeedbackDialog} is unmounted.
|
||||
*/
|
||||
onClose: Function,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
onClose: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link FeedbackDialog}.
|
||||
|
@ -86,20 +139,20 @@ type State = {
|
|||
/**
|
||||
* The currently entered feedback message.
|
||||
*/
|
||||
message: string,
|
||||
message: string;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* score having been selected.
|
||||
*/
|
||||
mousedOverScore: number,
|
||||
mousedOverScore: number;
|
||||
|
||||
/**
|
||||
* The currently selected score selection index. The score will not be 0
|
||||
* indexed so subtract one to map with SCORES.
|
||||
*/
|
||||
score: number
|
||||
score: number;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -109,13 +162,19 @@ type State = {
|
|||
*
|
||||
* @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
|
||||
* the constant SCORES. This pattern is used for binding event handlers only
|
||||
* 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.
|
||||
|
@ -123,7 +182,7 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
* @param {Object} props - The read-only React {@code Component} props with
|
||||
* which the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
const { _message, _score } = this.props;
|
||||
|
@ -157,8 +216,9 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
this._scoreClickConfigurations = SCORES.map((textKey, index) => {
|
||||
return {
|
||||
_onClick: () => this._onScoreSelect(index),
|
||||
_onKeyPres: e => {
|
||||
_onKeyDown: (e: React.KeyboardEvent) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
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.
|
||||
// By scrolling to the top we prevent hiding the feedback stars so the user knows those exist.
|
||||
this._onScrollTop = (node: ?Scrollable) => {
|
||||
node && node.scroll && node.scroll(0, 0);
|
||||
this._onScrollTop = (node: Scrollable | null) => {
|
||||
node?.scroll?.(0, 0);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -215,14 +275,14 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
const scoreToDisplayAsSelected
|
||||
= mousedOverScore > -1 ? mousedOverScore : score;
|
||||
|
||||
const { t } = this.props;
|
||||
const { classes, t } = this.props;
|
||||
|
||||
const scoreIcons = this._scoreClickConfigurations.map(
|
||||
(config, index) => {
|
||||
const isFilled = index <= scoreToDisplayAsSelected;
|
||||
const activeClass = isFilled ? 'active' : '';
|
||||
const className
|
||||
= `star-btn ${scoreAnimationClass} ${activeClass}`;
|
||||
= `${classes.starBtn} ${scoreAnimationClass} ${activeClass}`;
|
||||
|
||||
return (
|
||||
<span
|
||||
|
@ -230,19 +290,19 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
className = { className }
|
||||
key = { index }
|
||||
onClick = { config._onClick }
|
||||
onKeyPress = { config._onKeyPres }
|
||||
onKeyDown = { config._onKeyDown }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }
|
||||
{ ...(isMobileBrowser() ? {} : {
|
||||
onMouseOver: config._onMouseOver
|
||||
}) }>
|
||||
{ isFilled
|
||||
? <StarFilledIcon
|
||||
label = 'star-filled'
|
||||
size = 'xlarge' />
|
||||
: <StarIcon
|
||||
label = 'star'
|
||||
size = 'xlarge' /> }
|
||||
? <Icon
|
||||
size = { ICON_SIZE }
|
||||
src = { IconFavoriteSolid } />
|
||||
: <Icon
|
||||
size = { ICON_SIZE }
|
||||
src = { IconFavorite } /> }
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
@ -255,23 +315,24 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
}}
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onSubmit }
|
||||
size = 'large'
|
||||
titleKey = 'feedback.rateExperience'>
|
||||
<div className = 'feedback-dialog'>
|
||||
<div className = 'rating'>
|
||||
<div className = { classes.dialog }>
|
||||
<div className = { classes.rating }>
|
||||
<div
|
||||
aria-label = { this.props.t('feedback.star') }
|
||||
className = 'star-label' >
|
||||
className = { classes.ratingLabel } >
|
||||
<p id = 'starLabel'>
|
||||
{ t(SCORES[scoreToDisplayAsSelected]) }
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className = 'stars'
|
||||
className = { classes.stars }
|
||||
onMouseLeave = { this._onScoreContainerMouseLeave }>
|
||||
{ scoreIcons }
|
||||
</div>
|
||||
</div>
|
||||
<div className = 'details'>
|
||||
<div className = { classes.details }>
|
||||
<Input
|
||||
autoFocus = { true }
|
||||
id = 'feedbackTextArea'
|
||||
|
@ -285,8 +346,6 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
_onCancel: () => boolean;
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -304,8 +363,6 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
return true;
|
||||
}
|
||||
|
||||
_onMessageChange: (Object) => void;
|
||||
|
||||
/**
|
||||
* Updates the known entered feedback message.
|
||||
*
|
||||
|
@ -314,7 +371,7 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onMessageChange(newValue) {
|
||||
_onMessageChange(newValue: string) {
|
||||
this.setState({ message: newValue });
|
||||
}
|
||||
|
||||
|
@ -325,12 +382,10 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onScoreSelect(score) {
|
||||
_onScoreSelect(score: number) {
|
||||
this.setState({ score });
|
||||
}
|
||||
|
||||
_onScoreContainerMouseLeave: () => void;
|
||||
|
||||
/**
|
||||
* Sets the currently hovered score to null to indicate no hover is
|
||||
* occurring.
|
||||
|
@ -350,12 +405,10 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onScoreMouseOver(mousedOverScore) {
|
||||
_onScoreMouseOver(mousedOverScore: number) {
|
||||
this.setState({ mousedOverScore });
|
||||
}
|
||||
|
||||
_onSubmit: () => void;
|
||||
|
||||
/**
|
||||
* Dispatches the entered feedback for submission. The submitted score will
|
||||
* 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;
|
||||
}
|
||||
|
||||
_onScrollTop: (node: ?Scrollable) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -386,7 +437,7 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
* @returns {{
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { message, score } = state['features/feedback'];
|
||||
|
||||
return {
|
||||
|
@ -407,4 +458,4 @@ function _mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(FeedbackDialog));
|
||||
export default withStyles(styles)(translate(connect(_mapStateToProps)(FeedbackDialog)));
|
Loading…
Reference in New Issue