11import PropTypes from 'prop-types' ;
22import cx from 'classnames' ;
3- import React from 'react' ;
3+ import React , { useEffect , useRef , useState } from 'react' ;
44import { FormattedMessage , useIntl } from 'react-intl' ;
55import Icon from '../Icon' ;
66import { useConfigContext } from '../../configurations/ConfigContext' ;
77
8- export default function Feedback ( {
9- recommended, // true if ranked as best by personalization algo
10- feedback, // true=likes, false=dislikes, undefined=no feedback yet
11- giveFeedback, // callback to submit user's feedback action
12- } ) {
13- const intl = useIntl ( ) ;
14- const { colors } = useConfigContext ( ) ;
8+ const ANIMATION_MS = 1200 ;
159
16- let status ;
17- if ( feedback === true ) {
18- status = 'personalization-liked' ;
19- } else if ( feedback === false ) {
20- status = 'personalization-disliked' ;
21- } else {
22- status = 'personalization-ask' ;
23- }
10+ function FeedbackLayer ( { recommended, status, giveFeedback, animationClass } ) {
11+ const { colors } = useConfigContext ( ) ;
12+ const intl = useIntl ( ) ;
2413
2514 const favIcon = recommended
2615 ? 'icon_star-with-circle'
@@ -40,48 +29,130 @@ export default function Feedback({
4029 const iconProps = iconMap [ status ] ;
4130
4231 return (
43- < div className = "feedback-container" >
32+ < div className = { cx ( 'feedback-layer' , animationClass ) } >
33+ < div className = "feedback-container" >
34+ < div
35+ className = { cx ( 'feedback-section' , {
36+ 'feedback-text-posted' : status !== 'personalization-ask' ,
37+ } ) }
38+ >
39+ < Icon { ...iconProps } height = { 1.4 } width = { 1.4 } />
40+ < span > </ span >
41+ < FormattedMessage id = { status } />
42+ </ div >
43+ { status === 'personalization-ask' && (
44+ < div className = "feedback-section" >
45+ < button
46+ type = "button"
47+ className = "thumb-button"
48+ onClick = { ( ) => giveFeedback ( true ) }
49+ aria-label = { intl . formatMessage ( {
50+ id : 'personalization-aria-like' ,
51+ } ) }
52+ >
53+ < Icon
54+ img = "icon_thumb"
55+ color = { colors . primary }
56+ height = { 1 }
57+ width = { 1 }
58+ />
59+ </ button >
60+ < button
61+ type = "button"
62+ className = "thumb-button"
63+ onClick = { ( ) => giveFeedback ( false ) }
64+ aria-label = { intl . formatMessage ( {
65+ id : 'personalization-aria-dislike' ,
66+ } ) }
67+ >
68+ < Icon
69+ img = "icon_thumb-down"
70+ color = { colors . primary }
71+ height = { 1 }
72+ width = { 1 }
73+ />
74+ </ button >
75+ </ div >
76+ ) }
77+ </ div >
78+ </ div >
79+ ) ;
80+ }
81+
82+ FeedbackLayer . propTypes = {
83+ recommended : PropTypes . bool . isRequired ,
84+ status : PropTypes . string . isRequired ,
85+ giveFeedback : PropTypes . func . isRequired ,
86+ animationClass : PropTypes . string . isRequired ,
87+ } ;
88+
89+ export default function Feedback ( {
90+ recommended, // true if ranked as best by personalization algo
91+ feedback, // true=likes, false=dislikes, undefined=no feedback yet
92+ giveFeedback, // callback to submit user's feedback action
93+ } ) {
94+ const [ isAnimating , setIsAnimating ] = useState ( false ) ;
95+ const timerRef = useRef ( null ) ;
96+ const panelRef = useRef ( null ) ;
97+
98+ useEffect ( ( ) => {
99+ return ( ) => {
100+ if ( timerRef . current ) {
101+ clearTimeout ( timerRef . current ) ;
102+ }
103+ } ;
104+ } , [ ] ) ;
105+
106+ const handleGiveFeedback = value => {
107+ if ( isAnimating ) {
108+ return ;
109+ }
110+
111+ setIsAnimating ( true ) ;
112+ timerRef . current = setTimeout ( ( ) => {
113+ setIsAnimating ( false ) ;
114+ panelRef . current ?. focus ( ) ;
115+ } , ANIMATION_MS ) ;
116+ giveFeedback ( value ) ;
117+ } ;
118+
119+ let status ;
120+ if ( feedback === true ) {
121+ status = 'personalization-liked' ;
122+ } else if ( feedback === false ) {
123+ status = 'personalization-disliked' ;
124+ } else {
125+ status = 'personalization-ask' ;
126+ }
127+
128+ return (
129+ < div ref = { panelRef } className = "feedback-panel" tabIndex = "-1" >
130+ { ( status !== 'personalization-ask' || isAnimating ) && (
131+ < FeedbackLayer
132+ key = "knownstate"
133+ recommended = { recommended }
134+ status = { status }
135+ giveFeedback = { handleGiveFeedback }
136+ animationClass = { isAnimating ? 'enter' : '' }
137+ />
138+ ) }
139+ { ( status === 'personalization-ask' || isAnimating ) && (
140+ < FeedbackLayer
141+ key = "askstate"
142+ recommended = { recommended }
143+ status = "personalization-ask"
144+ giveFeedback = { handleGiveFeedback }
145+ animationClass = { isAnimating ? 'leave' : '' }
146+ />
147+ ) }
44148 < div
45- className = { cx ( 'feedback-section' , {
46- 'feedback-text-posted' : status !== 'personalization-ask' ,
47- } ) }
149+ className = "sr-only"
150+ aria-live = "polite"
151+ aria-atomic = "true"
152+ role = "status"
48153 >
49- < Icon { ...iconProps } height = { 1.4 } width = { 1.4 } />
50- < span > </ span >
51154 < FormattedMessage id = { status } />
52155 </ div >
53- { status === 'personalization-ask' && (
54- < div className = "feedback-section" >
55- < button
56- type = "button"
57- className = "thumb-button"
58- onClick = { ( ) => giveFeedback ( true ) }
59- aria-label = { intl . formatMessage ( { id : 'personalization-aria-like' } ) }
60- >
61- < Icon
62- img = "icon_thumb"
63- color = { colors . primary }
64- height = { 1 }
65- width = { 1 }
66- />
67- </ button >
68- < button
69- type = "button"
70- className = "thumb-button"
71- onClick = { ( ) => giveFeedback ( false ) }
72- aria-label = { intl . formatMessage ( {
73- id : 'personalization-aria-dislike' ,
74- } ) }
75- >
76- < Icon
77- img = "icon_thumb-down"
78- color = { colors . primary }
79- height = { 1 }
80- width = { 1 }
81- />
82- </ button >
83- </ div >
84- ) }
85156 </ div >
86157 ) ;
87158}
0 commit comments