Skip to content

Commit d774a0c

Browse files
committed
add skippable feedback step and rename textButtonLabel to textButtonSendLabel
adds textButtonSkipLabel and a skip button to the follow-up feedback screen, mirroring the existing email collection step. renames textButtonLabel to textButtonSendLabel for consistency with contactButtonSendLabel/contactButtonSkipLabel.
1 parent 56663bd commit d774a0c

18 files changed

Lines changed: 102 additions & 57 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- **Optional email collection** - New `collectContact`, `userId`, `contactQuestion`, `contactSubtext`, `contactButtonSendLabel`, and `contactButtonSkipLabel` props add an optional email collection screen before the success screen. Skipped automatically when `userId` is already known. New `onContactSubmit` event fires when a respondent submits an email.
13+
- **Skippable feedback step** - New `textButtonSkipLabel` prop adds a Skip button to the follow-up feedback screen, mirroring the email collection step. Skipping (or submitting with empty input) advances to the next screen without calling `onFeedbackSubmit`.
1314

1415
### Changed
1516

17+
- **BREAKING: `textButtonLabel` renamed to `textButtonSendLabel`** - Aligns naming with the new `textButtonSkipLabel` and the existing `contactButtonSendLabel`/`contactButtonSkipLabel` pair. Update your prop usage:
18+
```tsx
19+
<CSAT5Survey textButtonSendLabel="Send" textButtonSkipLabel="Skip" />
20+
```
1621
- **Feedback and label styling** - Added a focus outline to choice checkboxes, fixed font inheritance and checkmark proportions in feedback inputs, and prevented label overflow with `box-sizing: border-box`.
1722

1823
## [1.6.1] - 2026-07-10

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ import 'react-feedback-surveys/index.css';
102102
maxLabel="Very satisfied"
103103
responseType="text"
104104
textQuestion="We'd love to hear your thoughts — what can we improve?"
105-
textButtonLabel="Send"
105+
textButtonSendLabel="Send"
106+
textButtonSkipLabel="Skip"
106107
thankYouMessage="Thanks for your feedback!"
107108
onScoreSubmit={({ value }) => {/* ... */}}
108109
onFeedbackSubmit={({ value, text }) => {/* ... */}}
@@ -131,7 +132,8 @@ import 'react-feedback-surveys/index.css';
131132
question="Are you satisfied with the result?"
132133
responseType="text"
133134
textQuestion="We'd love to hear your thoughts — what can we improve?"
134-
textButtonLabel="Send"
135+
textButtonSendLabel="Send"
136+
textButtonSkipLabel="Skip"
135137
thankYouMessage="Thank you for your feedback!"
136138
onScoreSubmit={({ value }) => {/* ... */}}
137139
onFeedbackSubmit={({ value, text }) => {/* ... */}}
@@ -164,7 +166,8 @@ import 'react-feedback-surveys/index.css';
164166
maxLabel="Very likely"
165167
responseType="text"
166168
textQuestion="We'd love to hear your thoughts — what can we improve?"
167-
textButtonLabel="Send"
169+
textButtonSendLabel="Send"
170+
textButtonSkipLabel="Skip"
168171
thankYouMessage="Thank you for your feedback!"
169172
onScoreSubmit={({ value }) => {/* ... */}}
170173
onFeedbackSubmit={({ value, text }) => {/* ... */}}
@@ -195,7 +198,8 @@ import 'react-feedback-surveys/index.css';
195198
maxLabel="Very easy"
196199
responseType="text"
197200
textQuestion="We'd love to hear your thoughts — what can we improve?"
198-
textButtonLabel="Send"
201+
textButtonSendLabel="Send"
202+
textButtonSkipLabel="Skip"
199203
thankYouMessage="Thank you for your feedback!"
200204
onScoreSubmit={({ value }) => {/* ... */}}
201205
onFeedbackSubmit={({ value, text }) => {/* ... */}}
@@ -297,7 +301,8 @@ Most props are shared across all survey widgets. Each widget differs only in its
297301
| `maxLabel` | `string` | - | Right label for the scale. |
298302
| `responseType` | `null \| 'text' \| 'choices'` | - | Enables optional follow-up feedback. |
299303
| `textQuestion` | `string` | - | Follow-up question displayed when `responseType` is defined. |
300-
| `textButtonLabel` | `string` | - | Submit label for the feedback screen. |
304+
| `textButtonSendLabel` | `string` | - | Submit label for the feedback screen. |
305+
| `textButtonSkipLabel` | `string` | - | Skip label for the feedback screen. |
301306
| `choiceOptions` | `string[] \| null` | - | Predefined choices (when `responseType === 'choices'`). |
302307
| `thankYouMessage` | `string` | required | Message shown after submission. |
303308
| `collectContact` | `boolean` | - | Enables an optional email collection step before the success screen. |
@@ -367,6 +372,8 @@ The actual `value` returned depends on the survey type:
367372
Invoked when the user completes the follow-up step and submits their feedback (only applies when `responseType` is `text` or `choices`).
368373
This callback provides both the original score and the user's input.
369374

375+
The feedback step is always optional: respondents can submit feedback or skip it via the `textButtonSkipLabel` button (or by submitting with empty input), and either action advances to the next screen. `onFeedbackSubmit` only fires when feedback text or choices are actually submitted; it is **not** called when the step is skipped.
376+
370377
**Arguments:**
371378
- `value: number` — the same score previously passed to `onScoreSubmit`
372379
- `text: string | string[]` — depends on `responseType`:

docs/assets/ces7.png

0 Bytes
Loading

docs/assets/csat2.png

0 Bytes
Loading

docs/assets/nps10-mobile.png

0 Bytes
Loading

src/components/Feedback/Feedback.module.scss

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
}
7373

7474
.textarea {
75-
height: 192px;
75+
height: 120px;
7676
padding: 8px 12px;
7777
}
7878

@@ -94,14 +94,6 @@
9494
&:focus {
9595
outline: 4px solid hsl(var(--_color-outline) / 14%);
9696
}
97-
98-
&.invalid {
99-
border-color: hsl(var(--_color-error) / 100%);
100-
101-
&:focus {
102-
outline-color: hsl(var(--_color-error) / 14%);
103-
}
104-
}
10597
}
10698

10799
.submit {
@@ -112,6 +104,25 @@
112104
width: 100%;
113105
}
114106

107+
.skip {
108+
@include resetButton;
109+
110+
margin-top: 8px;
111+
display: block;
112+
width: 100%;
113+
padding: 4px;
114+
font-size: 16px;
115+
font-weight: 500;
116+
line-height: 1.5;
117+
text-align: center;
118+
color: hsl(var(--_color-muted) / 100%);
119+
cursor: pointer;
120+
121+
&:hover {
122+
color: hsl(var(--_color-text) / 100%);
123+
}
124+
}
125+
115126
.success {
116127
padding: 16px 0 32px;
117128
font-size: 16px;

src/components/Feedback/Feedback.tsx

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@ import styles from './Feedback.module.scss';
66

77
export interface FeedbackProps {
88
/** Submit button label */
9-
buttonLabel?: React.ReactNode;
9+
buttonSendLabel?: React.ReactNode;
10+
/** Skip button label */
11+
buttonSkipLabel?: React.ReactNode;
1012
/** Type of feedback collection */
1113
responseType?: SharedSurveyProps['responseType'];
1214
/** Optional predefined feedback choices */
1315
choiceOptions?: SharedSurveyProps['choiceOptions'];
14-
/** Callback when feedback is submitted */
15-
onSubmit?: (text: string | string[]) => void;
16+
/** Callback when feedback is submitted (omitted when skipped) */
17+
onSubmit?: (text?: string | string[]) => void;
1618
}
1719

1820
export const Feedback: React.FC<FeedbackProps> = ({
19-
buttonLabel = 'Submit',
21+
buttonSendLabel = 'Submit',
22+
buttonSkipLabel = 'Skip',
2023
responseType,
2124
choiceOptions,
2225
onSubmit
@@ -25,19 +28,14 @@ export const Feedback: React.FC<FeedbackProps> = ({
2528

2629
const [text, setText] = useState<string>('');
2730
const [selected, setSelected] = useState<string[]>([]);
28-
const [isInvalid, setIsInvalid] = useState<boolean>(false);
2931

3032
const onTextKeyDown = useCallback((event: React.KeyboardEvent<HTMLTextAreaElement | HTMLInputElement>): void => {
3133
event.stopPropagation();
3234
}, []);
3335

3436
const onTextChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>): void => {
3537
setText(event.currentTarget.value);
36-
37-
if (isInvalid) {
38-
setIsInvalid(false);
39-
}
40-
}, [isInvalid]);
38+
}, []);
4139

4240
const onChoiceChange = useCallback((event: React.ChangeEvent<HTMLInputElement>): void => {
4341
const { value, checked } = event.currentTarget;
@@ -61,11 +59,6 @@ export const Feedback: React.FC<FeedbackProps> = ({
6159
return;
6260
}
6361

64-
if (responseType === 'text' && !textValue) {
65-
setIsInvalid(true);
66-
return;
67-
}
68-
6962
onSubmit?.(text.trim());
7063
}, [
7164
text,
@@ -74,6 +67,10 @@ export const Feedback: React.FC<FeedbackProps> = ({
7467
onSubmit
7568
]);
7669

70+
const onSkip = useCallback((): void => {
71+
onSubmit?.(undefined);
72+
}, [onSubmit]);
73+
7774
if (!responseType) {
7875
return null;
7976
}
@@ -154,9 +151,7 @@ export const Feedback: React.FC<FeedbackProps> = ({
154151

155152
<textarea
156153
aria-label="Your feedback"
157-
aria-required="true"
158-
aria-invalid={isInvalid}
159-
className={`${styles.textarea} ${isInvalid ? styles.invalid : ''}`}
154+
className={styles.textarea}
160155
id={textareaId}
161156
maxLength={1000}
162157
name="feedback"
@@ -172,7 +167,15 @@ export const Feedback: React.FC<FeedbackProps> = ({
172167
className={styles.submit}
173168
type="submit"
174169
>
175-
{buttonLabel}
170+
{buttonSendLabel}
171+
</button>
172+
173+
<button
174+
className={styles.skip}
175+
type="button"
176+
onClick={onSkip}
177+
>
178+
{buttonSkipLabel}
176179
</button>
177180
</form>
178181
);

src/components/SurveyRoot/SurveyRoot.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ export interface SurveyRootProps {
2727
/** Follow-up feedback question */
2828
textQuestion?: SharedSurveyProps['textQuestion'];
2929
/** Submit button text */
30-
textButtonLabel?: SharedSurveyProps['textButtonLabel'];
30+
textButtonSendLabel?: SharedSurveyProps['textButtonSendLabel'];
31+
/** Skip button text */
32+
textButtonSkipLabel?: SharedSurveyProps['textButtonSkipLabel'];
3133
/** Type of feedback collection */
3234
responseType?: SharedSurveyProps['responseType']
3335
/** Optional predefined choices for feedback */
@@ -44,9 +46,9 @@ export interface SurveyRootProps {
4446
contactButtonSkipLabel?: SharedSurveyProps['contactButtonSkipLabel'];
4547
/** Current screen */
4648
screen: SurveyScreen;
47-
/** Callback when feedback is submitted */
48-
onFeedback?: (text: string | string[]) => void;
49-
/** Callback when contact info is submitted (omitted when skipped) */
49+
/** Callback when feedback is submitted (omitted when skipped) */
50+
onFeedback?: (text?: string | string[]) => void;
51+
/** Callback when contact is submitted (omitted when skipped) */
5052
onContact?: (email?: string) => void;
5153
}
5254

@@ -57,7 +59,8 @@ export const SurveyRoot: React.FC<SurveyRootProps> = ({
5759
dir,
5860
question,
5961
textQuestion,
60-
textButtonLabel,
62+
textButtonSendLabel,
63+
textButtonSkipLabel,
6164
responseType,
6265
choiceOptions,
6366
thankYouMessage,
@@ -114,7 +117,8 @@ export const SurveyRoot: React.FC<SurveyRootProps> = ({
114117
role="region"
115118
>
116119
<Feedback
117-
buttonLabel={textButtonLabel}
120+
buttonSendLabel={textButtonSendLabel}
121+
buttonSkipLabel={textButtonSkipLabel}
118122
choiceOptions={choiceOptions}
119123
responseType={responseType}
120124
onSubmit={onFeedback}

src/hooks/useSurveyState.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,9 @@ const useSurveyState = ({
8888
finishFlow
8989
]);
9090

91-
const onFeedbackChange = useCallback(async(text: string | string[]) => {
91+
const onFeedbackChange = useCallback(async(text?: string | string[]) => {
9292
if (!text?.length) {
93+
finishFlow();
9394
return;
9495
}
9596

src/surveys/CES7Survey/CES7Survey.stories.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ const commonProps: CES7SurveyProps = {
3636
minLabel: 'Very difficult',
3737
maxLabel: 'Very easy',
3838
textQuestion: 'We’d love to hear your thoughts — what can we improve?',
39-
textButtonLabel: 'Submit',
39+
textButtonSendLabel: 'Submit',
40+
textButtonSkipLabel: 'Skip',
4041
responseType: null,
4142
choiceOptions: null,
4243
thankYouMessage: 'Thank you for your feedback',

0 commit comments

Comments
 (0)