@@ -2,7 +2,69 @@ import React, { useState } from 'react'
22import styles from './styles.module.css'
33
44type Rating = 'yes' | 'no' | null
5- type Phase = 'initial' | 'comment' | 'submitted'
5+ type Phase = 'initial' | 'reason' | 'submitted'
6+
7+ interface FeedbackOption {
8+ value : string
9+ label : string
10+ description : string
11+ }
12+
13+ const YES_OPTIONS : FeedbackOption [ ] = [
14+ {
15+ value : 'accurate' ,
16+ label : 'Accurate' ,
17+ description : 'Accurately describes the product or feature.' ,
18+ } ,
19+ {
20+ value : 'solved' ,
21+ label : 'Solved my problem' ,
22+ description : 'Helped me resolve an issue.' ,
23+ } ,
24+ {
25+ value : 'easy' ,
26+ label : 'Easy to understand' ,
27+ description : 'Easy to follow and comprehend.' ,
28+ } ,
29+ {
30+ value : 'helpful' ,
31+ label : 'Helped me decide to use the product' ,
32+ description : 'Convinced me to adopt the product or feature.' ,
33+ } ,
34+ {
35+ value : 'other' ,
36+ label : 'Another reason' ,
37+ description : '' ,
38+ } ,
39+ ]
40+
41+ const NO_OPTIONS : FeedbackOption [ ] = [
42+ {
43+ value : 'inaccurate' ,
44+ label : 'Inaccurate' ,
45+ description : "Doesn't accurately describe the product or feature." ,
46+ } ,
47+ {
48+ value : 'missing-info' ,
49+ label : "Couldn't find what I was looking for" ,
50+ description : 'Missing important information.' ,
51+ } ,
52+ {
53+ value : 'hard-to-understand' ,
54+ label : 'Hard to understand' ,
55+ description : 'Too complicated or unclear.' ,
56+ } ,
57+ {
58+ value : 'code-errors' ,
59+ label : 'Code sample errors' ,
60+ description : 'One or more code samples are incorrect.' ,
61+ } ,
62+ {
63+ value : 'other' ,
64+ label : 'Another reason' ,
65+ description : '' ,
66+ } ,
67+ ]
668
769function stripHtml ( text : string ) : string {
870 let prev = text
@@ -29,27 +91,35 @@ declare global {
2991export default function FeedbackWidget ( ) : React . ReactNode {
3092 const [ rating , setRating ] = useState < Rating > ( null )
3193 const [ phase , setPhase ] = useState < Phase > ( 'initial' )
94+ const [ selectedOption , setSelectedOption ] = useState ( '' )
3295 const [ reason , setReason ] = useState ( '' )
3396 const [ errorMsg , setErrorMsg ] = useState ( '' )
3497 const [ submitting , setSubmitting ] = useState ( false )
3598
36- const reasonIsEmpty = ! sanitizeReason ( reason )
99+ const options = rating === 'yes' ? YES_OPTIONS : NO_OPTIONS
100+ const isOtherSelected = selectedOption === 'other'
101+ const canSubmit = selectedOption !== '' && ( ! isOtherSelected || sanitizeReason ( reason ) . length > 0 )
37102
38103 const handleRating = ( selected : Rating ) => {
39104 setRating ( selected )
40- setPhase ( 'comment' )
105+ setSelectedOption ( '' )
106+ setReason ( '' )
107+ setPhase ( 'reason' )
41108 }
42109
43110 const handleSubmit = async ( ) => {
44- if ( rating === 'no' && reasonIsEmpty ) return
111+ if ( ! canSubmit ) return
45112 setSubmitting ( true )
46113 setErrorMsg ( '' )
47114
115+ const reasonText = isOtherSelected ? reason . trim ( ) : ''
116+
48117 window . dataLayer ?. push ( {
49118 event : 'docs_feedback' ,
50119 page_url : window . location . pathname ,
51120 rating,
52- reason : reason . trim ( ) ,
121+ option : selectedOption ,
122+ reason : reasonText ,
53123 } )
54124
55125 try {
@@ -59,7 +129,8 @@ export default function FeedbackWidget(): React.ReactNode {
59129 body : JSON . stringify ( {
60130 page_url : window . location . pathname ,
61131 rating,
62- reason : reason . trim ( ) ,
132+ option : selectedOption ,
133+ reason : reasonText ,
63134 } ) ,
64135 } )
65136 if ( ! res . ok ) {
@@ -102,23 +173,45 @@ export default function FeedbackWidget(): React.ReactNode {
102173 </ >
103174 ) }
104175
105- { phase === 'comment ' && (
176+ { phase === 'reason ' && (
106177 < div className = { styles . commentSection } >
107- < label htmlFor = "feedback-reason" className = { styles . label } >
108- { rating === 'yes' ? 'What worked well? (optional)' : 'What went wrong?' }
109- </ label >
110- < textarea
111- id = "feedback-reason"
112- className = { styles . textarea }
113- value = { reason }
114- onChange = { e => setReason ( e . target . value ) }
115- placeholder = {
116- rating === 'yes' ? 'Tell us what you liked...' : 'Tell us what we can improve...'
117- }
118- maxLength = { 1000 }
119- rows = { 3 }
120- required = { rating === 'no' }
121- />
178+ < p className = { styles . label } >
179+ { rating === 'yes' ? 'What worked well?' : 'What went wrong?' }
180+ </ p >
181+ < div className = { styles . optionList } role = "radiogroup" >
182+ { options . map ( opt => (
183+ < label key = { opt . value } className = { styles . option } >
184+ < input
185+ type = "radio"
186+ name = "feedback-option"
187+ value = { opt . value }
188+ checked = { selectedOption === opt . value }
189+ onChange = { ( ) => setSelectedOption ( opt . value ) }
190+ className = { styles . radioInput }
191+ />
192+ < span className = { styles . optionText } >
193+ < span className = { styles . optionLabel } > { opt . label } </ span >
194+ { opt . description && (
195+ < span className = { styles . optionDescription } > { opt . description } </ span >
196+ ) }
197+ { opt . value === 'other' && isOtherSelected && (
198+ < textarea
199+ className = { styles . inlineTextarea }
200+ value = { reason }
201+ onChange = { e => {
202+ e . stopPropagation ( )
203+ setReason ( e . target . value )
204+ } }
205+ onClick = { e => e . stopPropagation ( ) }
206+ placeholder = "Tell us more..."
207+ maxLength = { 1000 }
208+ rows = { 7 }
209+ />
210+ ) }
211+ </ span >
212+ </ label >
213+ ) ) }
214+ </ div >
122215 { errorMsg && (
123216 < p className = { styles . error } role = "alert" >
124217 { errorMsg }
@@ -129,7 +222,7 @@ export default function FeedbackWidget(): React.ReactNode {
129222 type = "button"
130223 className = { styles . submitBtn }
131224 onClick = { handleSubmit }
132- disabled = { submitting || ( rating === 'no' && reasonIsEmpty ) } >
225+ disabled = { submitting || ! canSubmit } >
133226 { submitting ? 'Sending...' : errorMsg ? 'Try again' : 'Submit' }
134227 </ button >
135228 </ div >
0 commit comments