@redocly/theme 0.46.3 → 0.46.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Feedback/Mood.d.ts +3 -1
- package/lib/components/Feedback/Mood.js +32 -7
- package/lib/components/Feedback/Rating.d.ts +3 -0
- package/lib/components/Feedback/Rating.js +31 -6
- package/lib/components/Feedback/Scale.d.ts +3 -0
- package/lib/components/Feedback/Scale.js +29 -6
- package/lib/components/Feedback/Sentiment.d.ts +3 -1
- package/lib/components/Feedback/Sentiment.js +30 -5
- package/lib/core/hooks/use-theme-hooks.js +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/markdoc/components/Cards/Card.d.ts +1 -1
- package/lib/markdoc/components/Cards/Card.js +2 -2
- package/lib/markdoc/tags/markdoc-example.js +18 -1
- package/package.json +2 -2
- package/src/components/Feedback/Mood.tsx +66 -9
- package/src/components/Feedback/Rating.tsx +66 -8
- package/src/components/Feedback/Scale.tsx +58 -6
- package/src/components/Feedback/Sentiment.tsx +64 -7
- package/src/core/hooks/use-theme-hooks.ts +1 -0
- package/src/core/types/l10n.ts +2 -0
- package/src/markdoc/components/Cards/Card.tsx +3 -3
- package/src/markdoc/tags/markdoc-example.ts +27 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReasonsSettingsSchema } from '@redocly/config';
|
|
1
|
+
import type { AnonymousUserEmailSettings, ReasonsSettingsSchema } from '@redocly/config';
|
|
2
2
|
export declare enum MOOD_STATES {
|
|
3
3
|
SATISFIED = "satisfied",
|
|
4
4
|
NEUTRAL = "neutral",
|
|
@@ -9,6 +9,7 @@ export type MoodProps = {
|
|
|
9
9
|
score: number;
|
|
10
10
|
comment?: string;
|
|
11
11
|
reasons?: string[];
|
|
12
|
+
email?: string;
|
|
12
13
|
}) => void;
|
|
13
14
|
settings?: {
|
|
14
15
|
label?: string;
|
|
@@ -24,6 +25,7 @@ export type MoodProps = {
|
|
|
24
25
|
neutral?: ReasonsSettingsSchema;
|
|
25
26
|
dissatisfied?: ReasonsSettingsSchema;
|
|
26
27
|
};
|
|
28
|
+
anonymousUserEmail?: AnonymousUserEmailSettings;
|
|
27
29
|
};
|
|
28
30
|
className?: string;
|
|
29
31
|
};
|
|
@@ -31,10 +31,10 @@ exports.Mood = Mood;
|
|
|
31
31
|
const React = __importStar(require("react"));
|
|
32
32
|
const react_1 = require("react");
|
|
33
33
|
const styled_components_1 = __importDefault(require("styled-components"));
|
|
34
|
+
const Reasons_1 = require("../../components/Feedback/Reasons");
|
|
34
35
|
const hooks_1 = require("../../core/hooks");
|
|
35
36
|
const RadioCheckButtonIcon_1 = require("../../icons/RadioCheckButtonIcon/RadioCheckButtonIcon");
|
|
36
37
|
const Comment_1 = require("../../components/Feedback/Comment");
|
|
37
|
-
const Reasons_1 = require("../../components/Feedback/Reasons");
|
|
38
38
|
const Button_1 = require("../../components/Button/Button");
|
|
39
39
|
const FaceDissatisfiedIcon_1 = require("../../icons/FaceDissatisfiedIcon/FaceDissatisfiedIcon");
|
|
40
40
|
const FaceSatisfiedIcon_1 = require("../../icons/FaceSatisfiedIcon/FaceSatisfiedIcon");
|
|
@@ -46,13 +46,19 @@ var MOOD_STATES;
|
|
|
46
46
|
MOOD_STATES["DISSATISFIED"] = "dissatisfied";
|
|
47
47
|
})(MOOD_STATES || (exports.MOOD_STATES = MOOD_STATES = {}));
|
|
48
48
|
function Mood({ settings, onSubmit, className }) {
|
|
49
|
-
const { label, submitText, comment: commentSettings, reasons: reasonsSettings } = settings || {};
|
|
49
|
+
const { label, submitText, comment: commentSettings, reasons: reasonsSettings, anonymousUserEmail: anonymousUserEmailSettings, } = settings || {};
|
|
50
50
|
const [score, setScore] = React.useState('');
|
|
51
51
|
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
52
52
|
const [comment, setComment] = React.useState('');
|
|
53
53
|
const [reasons, setReasons] = React.useState([]);
|
|
54
|
-
const
|
|
54
|
+
const [email, setEmail] = React.useState();
|
|
55
|
+
const { useTranslate, useUserMenu } = (0, hooks_1.useThemeHooks)();
|
|
56
|
+
const { userData } = useUserMenu();
|
|
55
57
|
const { translate } = useTranslate();
|
|
58
|
+
const onEmailChange = (e) => {
|
|
59
|
+
const value = e.target.value;
|
|
60
|
+
setEmail(value || undefined);
|
|
61
|
+
};
|
|
56
62
|
const checkIfShouldDisplayReasons = (score) => {
|
|
57
63
|
if (!score || !reasonsSettings) {
|
|
58
64
|
return false;
|
|
@@ -103,23 +109,28 @@ function Mood({ settings, onSubmit, className }) {
|
|
|
103
109
|
const displayReasons = checkIfShouldDisplayReasons(score);
|
|
104
110
|
const displayComment = !!(score && !(commentSettings === null || commentSettings === void 0 ? void 0 : commentSettings.hide));
|
|
105
111
|
const displaySubmitBnt = !!(score && (displayReasons || displayComment));
|
|
112
|
+
const displayFeedbackEmail = !!score && (anonymousUserEmailSettings === null || anonymousUserEmailSettings === void 0 ? void 0 : anonymousUserEmailSettings.enabled) && !userData.isAuthenticated;
|
|
106
113
|
const onSubmitMoodForm = () => {
|
|
107
114
|
onSubmit({
|
|
108
115
|
score: remapScore(score),
|
|
109
116
|
comment,
|
|
110
117
|
reasons,
|
|
118
|
+
email,
|
|
111
119
|
});
|
|
112
120
|
setIsSubmitted(true);
|
|
113
121
|
};
|
|
114
122
|
const onCancelMoodForm = () => {
|
|
115
123
|
setScore('');
|
|
124
|
+
setComment('');
|
|
125
|
+
setReasons([]);
|
|
126
|
+
setEmail(undefined);
|
|
116
127
|
};
|
|
117
128
|
(0, react_1.useEffect)(() => {
|
|
118
|
-
if (score && !displayComment && !displayReasons) {
|
|
129
|
+
if (score && !displayComment && !displayReasons && !displayFeedbackEmail) {
|
|
119
130
|
onSubmitMoodForm();
|
|
120
131
|
}
|
|
121
132
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
122
|
-
}, [score, displayComment, displayReasons]);
|
|
133
|
+
}, [score, displayComment, displayReasons, displayFeedbackEmail]);
|
|
123
134
|
if (isSubmitted) {
|
|
124
135
|
return (React.createElement(MoodWrapper, { "data-component-name": "Feedback/Mood" },
|
|
125
136
|
React.createElement(StyledFormMandatoryFields, null,
|
|
@@ -128,7 +139,7 @@ function Mood({ settings, onSubmit, className }) {
|
|
|
128
139
|
React.createElement(RadioCheckButtonIcon_1.RadioCheckButtonIcon, null))));
|
|
129
140
|
}
|
|
130
141
|
return (React.createElement(MoodWrapper, { "data-component-name": "Feedback/Mood", className: className },
|
|
131
|
-
React.createElement(StyledForm,
|
|
142
|
+
React.createElement(StyledForm, { onSubmit: onSubmitMoodForm },
|
|
132
143
|
React.createElement(StyledFormMandatoryFields, null,
|
|
133
144
|
React.createElement(Label, { "data-translation-key": "feedback.settings.label" }, label || translate('feedback.settings.label', 'Was this helpful?')),
|
|
134
145
|
React.createElement(StyledMandatoryFieldContainer, null,
|
|
@@ -148,9 +159,14 @@ function Mood({ settings, onSubmit, className }) {
|
|
|
148
159
|
component: resolveReasonsComponent(score),
|
|
149
160
|
}, onChange: setReasons })),
|
|
150
161
|
displayComment && (React.createElement(Comment_1.Comment, { standAlone: false, onSubmit: ({ comment }) => setComment(comment), settings: { label: renderCommentLabel(score) } })))),
|
|
162
|
+
displayFeedbackEmail && (React.createElement(StyledFormOptionalFields, null,
|
|
163
|
+
React.createElement(Label, { "data-translation-key": "feedback.settings.anonymousUserEmail.label" }, (anonymousUserEmailSettings === null || anonymousUserEmailSettings === void 0 ? void 0 : anonymousUserEmailSettings.label) ||
|
|
164
|
+
translate('feedback.settings.anonymousUserEmail.label', 'Your email (optional, for follow-up)')),
|
|
165
|
+
React.createElement(EmailInput, { onChange: onEmailChange, placeholder: (anonymousUserEmailSettings === null || anonymousUserEmailSettings === void 0 ? void 0 : anonymousUserEmailSettings.placeholder) ||
|
|
166
|
+
translate('feedback.settings.anonymousUserEmail.placeholder', 'username@mail.com'), type: "email", required: !!email }))),
|
|
151
167
|
displaySubmitBnt && (React.createElement(ButtonsContainer, null,
|
|
152
168
|
React.createElement(Button_1.Button, { onClick: onCancelMoodForm, variant: "text", size: "small" }, "Cancel"),
|
|
153
|
-
React.createElement(Button_1.Button, {
|
|
169
|
+
React.createElement(Button_1.Button, { type: "submit", variant: "secondary", size: "small" }, "Submit"))))));
|
|
154
170
|
}
|
|
155
171
|
const remapScore = (score) => {
|
|
156
172
|
switch (score) {
|
|
@@ -208,4 +224,13 @@ const StyledMandatoryFieldContainer = styled_components_1.default.div `
|
|
|
208
224
|
align-items: center;
|
|
209
225
|
gap: var(--spacing-xxs);
|
|
210
226
|
`;
|
|
227
|
+
const EmailInput = styled_components_1.default.input `
|
|
228
|
+
background-color: var(--bg-color);
|
|
229
|
+
border-radius: var(--border-radius-lg);
|
|
230
|
+
border: var(--input-border);
|
|
231
|
+
outline: none;
|
|
232
|
+
color: var(--feedback-text-color);
|
|
233
|
+
font-family: var(--feedback-font-family);
|
|
234
|
+
padding: 10px;
|
|
235
|
+
`;
|
|
211
236
|
//# sourceMappingURL=Mood.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AnonymousUserEmailSettings } from '@redocly/config';
|
|
1
2
|
export declare const FEEDBACK_MAX_RATING = 5;
|
|
2
3
|
export type RatingProps = {
|
|
3
4
|
onSubmit: (value: {
|
|
@@ -5,6 +6,7 @@ export type RatingProps = {
|
|
|
5
6
|
comment?: string;
|
|
6
7
|
reasons?: string[];
|
|
7
8
|
max: number;
|
|
9
|
+
email?: string;
|
|
8
10
|
}) => void;
|
|
9
11
|
settings?: {
|
|
10
12
|
label?: string;
|
|
@@ -19,6 +21,7 @@ export type RatingProps = {
|
|
|
19
21
|
component?: string;
|
|
20
22
|
items: string[];
|
|
21
23
|
};
|
|
24
|
+
anonymousUserEmail?: AnonymousUserEmailSettings;
|
|
22
25
|
};
|
|
23
26
|
className?: string;
|
|
24
27
|
};
|
|
@@ -29,44 +29,55 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
29
29
|
exports.FEEDBACK_MAX_RATING = void 0;
|
|
30
30
|
exports.Rating = Rating;
|
|
31
31
|
const React = __importStar(require("react"));
|
|
32
|
-
const styled_components_1 = __importDefault(require("styled-components"));
|
|
33
32
|
const react_1 = require("react");
|
|
33
|
+
const styled_components_1 = __importDefault(require("styled-components"));
|
|
34
|
+
const Reasons_1 = require("../../components/Feedback/Reasons");
|
|
34
35
|
const hooks_1 = require("../../core/hooks");
|
|
35
36
|
const RadioCheckButtonIcon_1 = require("../../icons/RadioCheckButtonIcon/RadioCheckButtonIcon");
|
|
36
37
|
const Comment_1 = require("../../components/Feedback/Comment");
|
|
37
|
-
const Reasons_1 = require("../../components/Feedback/Reasons");
|
|
38
38
|
const Stars_1 = require("../../components/Feedback/Stars");
|
|
39
39
|
const Button_1 = require("../../components/Button/Button");
|
|
40
40
|
exports.FEEDBACK_MAX_RATING = 5;
|
|
41
41
|
function Rating({ settings, onSubmit, className }) {
|
|
42
|
-
const { label, submitText, comment: commentSettings, reasons: reasonsSettings } = settings || {};
|
|
42
|
+
const { label, submitText, comment: commentSettings, reasons: reasonsSettings, anonymousUserEmail: anonymousUserEmailSettings, } = settings || {};
|
|
43
43
|
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
44
44
|
const [score, setScore] = React.useState(0);
|
|
45
45
|
const [reasons, setReasons] = React.useState([]);
|
|
46
46
|
const [comment, setComment] = React.useState('');
|
|
47
|
-
const
|
|
47
|
+
const [email, setEmail] = React.useState();
|
|
48
|
+
const { useTranslate, useUserMenu } = (0, hooks_1.useThemeHooks)();
|
|
49
|
+
const { userData } = useUserMenu();
|
|
48
50
|
const { translate } = useTranslate();
|
|
51
|
+
const onEmailChange = (e) => {
|
|
52
|
+
const value = e.target.value;
|
|
53
|
+
setEmail(value || undefined);
|
|
54
|
+
};
|
|
49
55
|
const onSubmitRatingForm = () => {
|
|
50
56
|
onSubmit({
|
|
51
57
|
score,
|
|
52
58
|
comment,
|
|
53
59
|
reasons,
|
|
54
60
|
max: exports.FEEDBACK_MAX_RATING,
|
|
61
|
+
email,
|
|
55
62
|
});
|
|
56
63
|
setIsSubmitted(true);
|
|
57
64
|
};
|
|
58
65
|
const onCancelRatingForm = () => {
|
|
59
66
|
setScore(0);
|
|
67
|
+
setComment('');
|
|
68
|
+
setReasons([]);
|
|
69
|
+
setEmail(undefined);
|
|
60
70
|
};
|
|
61
71
|
const displayReasons = !!(score && reasonsSettings && !reasonsSettings.hide);
|
|
62
72
|
const displayComment = !!(score && !(commentSettings === null || commentSettings === void 0 ? void 0 : commentSettings.hide));
|
|
63
73
|
const displaySubmitBnt = !!(score && (displayReasons || displayComment));
|
|
74
|
+
const displayFeedbackEmail = !!score && (anonymousUserEmailSettings === null || anonymousUserEmailSettings === void 0 ? void 0 : anonymousUserEmailSettings.enabled) && !userData.isAuthenticated;
|
|
64
75
|
(0, react_1.useEffect)(() => {
|
|
65
|
-
if (score && !displayComment && !displayReasons) {
|
|
76
|
+
if (score && !displayComment && !displayReasons && !displayFeedbackEmail) {
|
|
66
77
|
onSubmitRatingForm();
|
|
67
78
|
}
|
|
68
79
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
69
|
-
}, [score, displayComment, displayReasons]);
|
|
80
|
+
}, [score, displayComment, displayReasons, displayFeedbackEmail]);
|
|
70
81
|
if (isSubmitted) {
|
|
71
82
|
return (React.createElement(RatingWrapper, { "data-component-name": "Feedback/Rating" },
|
|
72
83
|
React.createElement(StyledFormMandatoryFields, null,
|
|
@@ -90,6 +101,11 @@ function Rating({ settings, onSubmit, className }) {
|
|
|
90
101
|
label: (commentSettings === null || commentSettings === void 0 ? void 0 : commentSettings.label) ||
|
|
91
102
|
translate('feedback.settings.comment.label', 'Please share your feedback with us.'),
|
|
92
103
|
} })))),
|
|
104
|
+
displayFeedbackEmail && (React.createElement(StyledFormOptionalFields, null,
|
|
105
|
+
React.createElement(Label, { "data-translation-key": "feedback.settings.anonymousUserEmail.label" }, (anonymousUserEmailSettings === null || anonymousUserEmailSettings === void 0 ? void 0 : anonymousUserEmailSettings.label) ||
|
|
106
|
+
translate('feedback.settings.anonymousUserEmail.label', 'Your email (optional, for follow-up)')),
|
|
107
|
+
React.createElement(EmailInput, { onChange: onEmailChange, placeholder: (anonymousUserEmailSettings === null || anonymousUserEmailSettings === void 0 ? void 0 : anonymousUserEmailSettings.placeholder) ||
|
|
108
|
+
translate('feedback.settings.anonymousUserEmail.placeholder', 'username@mail.com'), type: "email", required: !!email }))),
|
|
93
109
|
displaySubmitBnt && (React.createElement(ButtonsContainer, null,
|
|
94
110
|
React.createElement(Button_1.Button, { onClick: onCancelRatingForm, variant: "text", size: "small" }, "Cancel"),
|
|
95
111
|
React.createElement(Button_1.Button, { onClick: onSubmitRatingForm, variant: "secondary", size: "small" }, "Submit"))))));
|
|
@@ -133,4 +149,13 @@ const ButtonsContainer = styled_components_1.default.div `
|
|
|
133
149
|
margin-bottom: var(--spacing-xxs);
|
|
134
150
|
gap: var(--spacing-xxs);
|
|
135
151
|
`;
|
|
152
|
+
const EmailInput = styled_components_1.default.input `
|
|
153
|
+
background-color: var(--bg-color);
|
|
154
|
+
border-radius: var(--border-radius-lg);
|
|
155
|
+
border: var(--input-border);
|
|
156
|
+
outline: none;
|
|
157
|
+
color: var(--feedback-text-color);
|
|
158
|
+
font-family: var(--feedback-font-family);
|
|
159
|
+
padding: 10px;
|
|
160
|
+
`;
|
|
136
161
|
//# sourceMappingURL=Rating.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AnonymousUserEmailSettings } from '@redocly/config';
|
|
1
2
|
export declare const MAX_SCALE = 10;
|
|
2
3
|
export type ScaleProps = {
|
|
3
4
|
onSubmit: (value: {
|
|
@@ -5,6 +6,7 @@ export type ScaleProps = {
|
|
|
5
6
|
comment?: string;
|
|
6
7
|
reasons?: string[];
|
|
7
8
|
max?: number;
|
|
9
|
+
email?: string;
|
|
8
10
|
}) => void;
|
|
9
11
|
settings?: {
|
|
10
12
|
label?: string;
|
|
@@ -21,6 +23,7 @@ export type ScaleProps = {
|
|
|
21
23
|
component?: string;
|
|
22
24
|
items: string[];
|
|
23
25
|
};
|
|
26
|
+
anonymousUserEmail?: AnonymousUserEmailSettings;
|
|
24
27
|
};
|
|
25
28
|
className?: string;
|
|
26
29
|
};
|
|
@@ -39,13 +39,19 @@ const Reasons_1 = require("../../components/Feedback/Reasons");
|
|
|
39
39
|
const Button_1 = require("../../components/Button/Button");
|
|
40
40
|
exports.MAX_SCALE = 10;
|
|
41
41
|
function Scale({ settings, onSubmit, className }) {
|
|
42
|
-
const { label, submitText, leftScaleLabel, rightScaleLabel, comment: commentSettings, reasons: reasonsSettings, } = settings || {};
|
|
42
|
+
const { label, submitText, leftScaleLabel, rightScaleLabel, comment: commentSettings, reasons: reasonsSettings, anonymousUserEmail: anonymousUserEmailSettings, } = settings || {};
|
|
43
43
|
const [score, setScore] = React.useState(0);
|
|
44
44
|
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
45
45
|
const [comment, setComment] = React.useState('');
|
|
46
46
|
const [reasons, setReasons] = React.useState([]);
|
|
47
|
-
const
|
|
47
|
+
const [email, setEmail] = React.useState();
|
|
48
|
+
const { useTranslate, useUserMenu } = (0, hooks_1.useThemeHooks)();
|
|
49
|
+
const { userData } = useUserMenu();
|
|
48
50
|
const { translate } = useTranslate();
|
|
51
|
+
const onEmailChange = (e) => {
|
|
52
|
+
const value = e.target.value;
|
|
53
|
+
setEmail(value || undefined);
|
|
54
|
+
};
|
|
49
55
|
const scaleOptions = [];
|
|
50
56
|
for (let i = 1; i <= exports.MAX_SCALE; i++) {
|
|
51
57
|
scaleOptions.push(React.createElement(Button_1.Button, { id: `scale-option-${i}`, key: `scale-option-${i}`, type: "button", onClick: () => setScore(i), className: `${score === i ? 'active' : ''}` }, i));
|
|
@@ -53,10 +59,12 @@ function Scale({ settings, onSubmit, className }) {
|
|
|
53
59
|
const displayReasons = !!score && reasonsSettings && !reasonsSettings.hide;
|
|
54
60
|
const displayComment = !!(score && !(commentSettings === null || commentSettings === void 0 ? void 0 : commentSettings.hide));
|
|
55
61
|
const displaySubmitBnt = !!score && (displayReasons || displayComment);
|
|
62
|
+
const displayFeedbackEmail = !!score && (anonymousUserEmailSettings === null || anonymousUserEmailSettings === void 0 ? void 0 : anonymousUserEmailSettings.enabled) && !userData.isAuthenticated;
|
|
56
63
|
const handleCancel = () => {
|
|
57
64
|
setScore(0);
|
|
58
65
|
setComment('');
|
|
59
66
|
setReasons([]);
|
|
67
|
+
setEmail(undefined);
|
|
60
68
|
};
|
|
61
69
|
const onSubmitScaleForm = () => {
|
|
62
70
|
onSubmit({
|
|
@@ -64,15 +72,16 @@ function Scale({ settings, onSubmit, className }) {
|
|
|
64
72
|
comment,
|
|
65
73
|
reasons,
|
|
66
74
|
max: exports.MAX_SCALE,
|
|
75
|
+
email,
|
|
67
76
|
});
|
|
68
77
|
setIsSubmitted(true);
|
|
69
78
|
};
|
|
70
79
|
(0, react_1.useEffect)(() => {
|
|
71
|
-
if (score && !displayComment && !displayReasons) {
|
|
80
|
+
if (score && !displayComment && !displayReasons && !displayFeedbackEmail) {
|
|
72
81
|
onSubmitScaleForm();
|
|
73
82
|
}
|
|
74
83
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
75
|
-
}, [score, displayComment, displayReasons]);
|
|
84
|
+
}, [score, displayComment, displayReasons, displayFeedbackEmail]);
|
|
76
85
|
if (isSubmitted) {
|
|
77
86
|
return (React.createElement(ScaleWrapper, { "data-component-name": "Feedback/Scale" },
|
|
78
87
|
React.createElement(Label, { "data-translation-key": "feedback.settings.submitText" }, submitText ||
|
|
@@ -80,7 +89,7 @@ function Scale({ settings, onSubmit, className }) {
|
|
|
80
89
|
React.createElement(RadioCheckButtonIcon_1.RadioCheckButtonIcon, null)));
|
|
81
90
|
}
|
|
82
91
|
return (React.createElement(ScaleWrapper, { "data-component-name": "Feedback/Scale", className: className },
|
|
83
|
-
React.createElement(StyledForm,
|
|
92
|
+
React.createElement(StyledForm, { onSubmit: onSubmitScaleForm },
|
|
84
93
|
React.createElement(StyledFormMandatoryFields, null,
|
|
85
94
|
React.createElement(Label, { "data-translation-key": "feedback.settings.label" }, label || translate('feedback.settings.label', 'Was this helpful?')),
|
|
86
95
|
React.createElement(ScaleOptionsWrapper, null, scaleOptions),
|
|
@@ -99,9 +108,14 @@ function Scale({ settings, onSubmit, className }) {
|
|
|
99
108
|
label: (commentSettings === null || commentSettings === void 0 ? void 0 : commentSettings.label) ||
|
|
100
109
|
translate('feedback.settings.comment.label', 'Please share your feedback with us.'),
|
|
101
110
|
} }))),
|
|
111
|
+
displayFeedbackEmail && (React.createElement(StyledFormOptionalFields, null,
|
|
112
|
+
React.createElement(Label, { "data-translation-key": "feedback.settings.anonymousUserEmail.label" }, (anonymousUserEmailSettings === null || anonymousUserEmailSettings === void 0 ? void 0 : anonymousUserEmailSettings.label) ||
|
|
113
|
+
translate('feedback.settings.anonymousUserEmail.label', 'Your email (optional, for follow-up)')),
|
|
114
|
+
React.createElement(EmailInput, { onChange: onEmailChange, placeholder: (anonymousUserEmailSettings === null || anonymousUserEmailSettings === void 0 ? void 0 : anonymousUserEmailSettings.placeholder) ||
|
|
115
|
+
translate('feedback.settings.anonymousUserEmail.placeholder', 'username@mail.com'), type: "email", required: !!email }))),
|
|
102
116
|
displaySubmitBnt && (React.createElement(ButtonsContainer, null,
|
|
103
117
|
React.createElement(Button_1.Button, { "data-translation-key": "feedback.settings.comment.cancel", onClick: handleCancel, variant: "text", size: "small" }, translate('feedback.settings.comment.cancel', 'Cancel')),
|
|
104
|
-
React.createElement(Button_1.Button, { "data-translation-key": "feedback.settings.scale.send",
|
|
118
|
+
React.createElement(Button_1.Button, { "data-translation-key": "feedback.settings.scale.send", type: "submit", variant: "secondary", size: "small" }, translate('feedback.settings.scale.send', 'Submit')))))));
|
|
105
119
|
}
|
|
106
120
|
const ScaleWrapper = styled_components_1.default.div `
|
|
107
121
|
font-family: var(--feedback-font-family);
|
|
@@ -172,4 +186,13 @@ const ScaleOptionsWrapper = styled_components_1.default.div `
|
|
|
172
186
|
gap: 2px;
|
|
173
187
|
}
|
|
174
188
|
`;
|
|
189
|
+
const EmailInput = styled_components_1.default.input `
|
|
190
|
+
background-color: var(--bg-color);
|
|
191
|
+
border-radius: var(--border-radius-lg);
|
|
192
|
+
border: var(--input-border);
|
|
193
|
+
outline: none;
|
|
194
|
+
color: var(--feedback-text-color);
|
|
195
|
+
font-family: var(--feedback-font-family);
|
|
196
|
+
padding: 10px;
|
|
197
|
+
`;
|
|
175
198
|
//# sourceMappingURL=Scale.js.map
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { ReasonsSettingsSchema } from '@redocly/config';
|
|
1
|
+
import type { ReasonsSettingsSchema, AnonymousUserEmailSettings } from '@redocly/config';
|
|
2
2
|
export type SentimentProps = {
|
|
3
3
|
onSubmit: (value: {
|
|
4
4
|
score: number;
|
|
5
5
|
comment?: string;
|
|
6
6
|
reasons?: string[];
|
|
7
|
+
email?: string;
|
|
7
8
|
}) => void;
|
|
8
9
|
settings?: {
|
|
9
10
|
label?: string;
|
|
@@ -17,6 +18,7 @@ export type SentimentProps = {
|
|
|
17
18
|
like?: ReasonsSettingsSchema;
|
|
18
19
|
dislike?: ReasonsSettingsSchema;
|
|
19
20
|
};
|
|
21
|
+
anonymousUserEmail?: AnonymousUserEmailSettings;
|
|
20
22
|
};
|
|
21
23
|
className?: string;
|
|
22
24
|
};
|
|
@@ -30,21 +30,27 @@ exports.Sentiment = Sentiment;
|
|
|
30
30
|
const React = __importStar(require("react"));
|
|
31
31
|
const react_1 = require("react");
|
|
32
32
|
const styled_components_1 = __importDefault(require("styled-components"));
|
|
33
|
+
const Reasons_1 = require("../../components/Feedback/Reasons");
|
|
33
34
|
const hooks_1 = require("../../core/hooks");
|
|
34
35
|
const RadioCheckButtonIcon_1 = require("../../icons/RadioCheckButtonIcon/RadioCheckButtonIcon");
|
|
35
36
|
const Comment_1 = require("../../components/Feedback/Comment");
|
|
36
|
-
const Reasons_1 = require("../../components/Feedback/Reasons");
|
|
37
37
|
const Button_1 = require("../../components/Button/Button");
|
|
38
38
|
const ThumbDownIcon_1 = require("../../icons/ThumbDownIcon/ThumbDownIcon");
|
|
39
39
|
const ThumbUpIcon_1 = require("../../icons/ThumbUpIcon/ThumbUpIcon");
|
|
40
40
|
function Sentiment({ settings, onSubmit, className }) {
|
|
41
|
-
const { label, submitText, comment: commentSettings, reasons: reasonsSettings } = settings || {};
|
|
41
|
+
const { label, submitText, comment: commentSettings, reasons: reasonsSettings, anonymousUserEmail: anonymousUserEmailSettings, } = settings || {};
|
|
42
42
|
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
43
43
|
const [score, setScore] = React.useState(0);
|
|
44
44
|
const [comment, setComment] = React.useState('');
|
|
45
45
|
const [reasons, setReasons] = React.useState([]);
|
|
46
|
-
const
|
|
46
|
+
const [email, setEmail] = React.useState();
|
|
47
|
+
const { useTranslate, useUserMenu } = (0, hooks_1.useThemeHooks)();
|
|
48
|
+
const { userData } = useUserMenu();
|
|
47
49
|
const { translate } = useTranslate();
|
|
50
|
+
const onEmailChange = (e) => {
|
|
51
|
+
const value = e.target.value;
|
|
52
|
+
setEmail(value || undefined);
|
|
53
|
+
};
|
|
48
54
|
const getScoreSpecificReasonSettings = (score) => {
|
|
49
55
|
switch (score) {
|
|
50
56
|
case 1:
|
|
@@ -68,6 +74,7 @@ function Sentiment({ settings, onSubmit, className }) {
|
|
|
68
74
|
const displayReasons = checkIfShouldDisplayReasons(score);
|
|
69
75
|
const displayComment = !!(score && !(commentSettings === null || commentSettings === void 0 ? void 0 : commentSettings.hide));
|
|
70
76
|
const displaySubmitBnt = !!(score && (displayReasons || displayComment));
|
|
77
|
+
const displayFeedbackEmail = !!score && (anonymousUserEmailSettings === null || anonymousUserEmailSettings === void 0 ? void 0 : anonymousUserEmailSettings.enabled) && !userData.isAuthenticated;
|
|
71
78
|
const commentLabel = score === 1
|
|
72
79
|
? (commentSettings && commentSettings.likeLabel) ||
|
|
73
80
|
translate('feedback.settings.comment.likeLabel', 'What was most helpful?')
|
|
@@ -92,18 +99,22 @@ function Sentiment({ settings, onSubmit, className }) {
|
|
|
92
99
|
score,
|
|
93
100
|
comment,
|
|
94
101
|
reasons,
|
|
102
|
+
email,
|
|
95
103
|
});
|
|
96
104
|
setIsSubmitted(true);
|
|
97
105
|
};
|
|
98
106
|
const onCancelSentimentForm = () => {
|
|
99
107
|
setScore(0);
|
|
108
|
+
setComment('');
|
|
109
|
+
setReasons([]);
|
|
110
|
+
setEmail(undefined);
|
|
100
111
|
};
|
|
101
112
|
(0, react_1.useEffect)(() => {
|
|
102
|
-
if (score && !displayComment && !displayReasons) {
|
|
113
|
+
if (score && !displayComment && !displayReasons && !displayFeedbackEmail) {
|
|
103
114
|
onSubmitSentimentForm();
|
|
104
115
|
}
|
|
105
116
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
106
|
-
}, [score, displayComment, displayReasons]);
|
|
117
|
+
}, [score, displayComment, displayReasons, displayFeedbackEmail]);
|
|
107
118
|
if (isSubmitted) {
|
|
108
119
|
return (React.createElement(SentimentWrapper, { "data-component-name": "Feedback/Sentiment" },
|
|
109
120
|
React.createElement(StyledFormMandatoryFields, null,
|
|
@@ -129,6 +140,11 @@ function Sentiment({ settings, onSubmit, className }) {
|
|
|
129
140
|
component: resolveReasonsComponent(score),
|
|
130
141
|
}, onChange: setReasons })),
|
|
131
142
|
displayComment && (React.createElement(Comment_1.Comment, { standAlone: false, onSubmit: ({ comment }) => setComment(comment), settings: { label: commentLabel } })))),
|
|
143
|
+
displayFeedbackEmail && (React.createElement(StyledFormOptionalFields, null,
|
|
144
|
+
React.createElement(Label, { "data-translation-key": "feedback.settings.anonymousUserEmail.label" }, (anonymousUserEmailSettings === null || anonymousUserEmailSettings === void 0 ? void 0 : anonymousUserEmailSettings.label) ||
|
|
145
|
+
translate('feedback.settings.anonymousUserEmail.label', 'Your email (optional, for follow-up)')),
|
|
146
|
+
React.createElement(EmailInput, { onChange: onEmailChange, placeholder: (anonymousUserEmailSettings === null || anonymousUserEmailSettings === void 0 ? void 0 : anonymousUserEmailSettings.placeholder) ||
|
|
147
|
+
translate('feedback.settings.anonymousUserEmail.placeholder', 'username@mail.com'), type: "email", required: !!email }))),
|
|
132
148
|
displaySubmitBnt && (React.createElement(ButtonsContainer, null,
|
|
133
149
|
React.createElement(Button_1.Button, { onClick: onCancelSentimentForm, variant: "text", size: "small" }, "Cancel"),
|
|
134
150
|
React.createElement(Button_1.Button, { onClick: onSubmitSentimentForm, variant: "secondary", size: "small" }, "Submit"))))));
|
|
@@ -182,4 +198,13 @@ const ButtonsContainer = styled_components_1.default.div `
|
|
|
182
198
|
margin-bottom: var(--spacing-xxs);
|
|
183
199
|
gap: var(--spacing-xxs);
|
|
184
200
|
`;
|
|
201
|
+
const EmailInput = styled_components_1.default.input `
|
|
202
|
+
background-color: var(--bg-color);
|
|
203
|
+
border-radius: var(--border-radius-lg);
|
|
204
|
+
border: var(--input-border);
|
|
205
|
+
outline: none;
|
|
206
|
+
color: var(--feedback-text-color);
|
|
207
|
+
font-family: var(--feedback-font-family);
|
|
208
|
+
padding: 10px;
|
|
209
|
+
`;
|
|
185
210
|
//# sourceMappingURL=Sentiment.js.map
|
|
@@ -11,6 +11,7 @@ const fallbacks = {
|
|
|
11
11
|
useTelemetry: () => ({ telemetry: () => { } }),
|
|
12
12
|
useBreadcrumbs: () => [],
|
|
13
13
|
useCodeHighlight: () => ({ highlight: (rawContent) => rawContent }),
|
|
14
|
+
useUserMenu: () => ({}),
|
|
14
15
|
};
|
|
15
16
|
const useThemeHooks = () => {
|
|
16
17
|
const context = (0, react_1.useContext)(contexts_1.ThemeDataContext);
|
package/lib/core/types/l10n.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TOptions } from 'i18next';
|
|
2
|
-
export type TranslationKey = 'dev.newApp' | 'dev.newApp.text' | 'dev.sidebar.header' | 'dev.sidebar.footer.text' | 'dev.create.app.dialog.appName.placeholder' | 'dev.create.app.dialog.appName.error' | 'dev.create.app.dialog.selectAPIs' | 'dev.create.app.dialog.description' | 'dev.create.app.dialog.description.placeholder' | 'dev.create.app.dialog.create' | 'dev.create.app.dialog.cancel' | 'dev.main.tab.appKeys' | 'dev.main.tab.logs' | 'dev.app.description.title' | 'dev.edit.description.dialog.title' | 'dev.edit.description.dialog.save' | 'dev.edit.description.dialog.cancel' | 'dev.edit.apis.dialog.selectedAPIs' | 'dev.app.key.create' | 'dev.create.key.dialog.title' | 'dev.create.key.dialog.create' | 'dev.create.key.dialog.cancel' | 'dev.app.edit' | 'dev.app.delete' | 'dev.edit.app.dialog.title' | 'dev.edit.app.dialog.save' | 'dev.edit.app.dialog.cancel' | 'dev.delete.app.dialog.title' | 'dev.delete.app.dialog.confirmation' | 'dev.delete.app.dialog.delete' | 'dev.delete.app.dialog.cancel' | 'dev.app.key.roll' | 'dev.roll.key.dialog.title' | 'dev.roll.key.dialog.apiKey' | 'dev.roll.key.dialog.expires' | 'dev.roll.key.dialog.confirmation' | 'dev.roll.key.dialog.cancel' | 'dev.roll.key.dialog.roll' | 'dev.update.key.dialog.title' | 'dev.update.key.dialog.update' | 'dev.update.key.dialog.cancel' | 'dev.app.key.api.name' | 'dev.app.key.api.status' | 'dev.app.key.api.edit' | 'dev.edit.apis.dialog.title' | 'dev.edit.apis.dialog.apiKey' | 'dev.edit.apis.dialog.save' | 'dev.edit.apis.dialog.cancel' | 'dev.select.placeholder' | 'dev.app.overview.status.pending' | 'dev.app.overview.status.approved' | 'dev.app.overview.status.revoked' | 'dev.app.overview.status' | 'dev.app.overview.non-production' | 'dev.app.overview.production' | 'dev.app.overview.clientId' | 'dev.app.overview.apiKey' | 'dev.app.key.revoke' | 'dev.revoke.key.dialog.title' | 'dev.revoke.key.dialog.apiKey' | 'dev.revoke.key.dialog.expires' | 'dev.revoke.key.dialog.confirmation' | 'dev.revoke.key.dialog.revoke' | 'dev.revoke.key.dialog.cancel' | 'dev.app.overview.expires' | 'dev.app.overview.created' | 'dev.app.overview.visibilityToggle.hide' | 'dev.app.overview.visibilityToggle.show' | 'search.loading' | 'search.noResults.title' | 'search.keys.navigate' | 'search.keys.select' | 'search.keys.exit' | 'search.label' | 'search.cancel' | 'search.recent' | 'search.navbar.label' | 'search.suggested' | 'search.showMore' | 'search.filter.title' | 'search.filter.reset' | 'search.filter.field.reset' | 'search.ai.thinkingText' | 'search.ai.resourcesFound' | 'search.aiButton' | 'search.ai.label' | 'toc.header' | 'footer.copyrightText' | 'page.homeButton' | 'page.forbidden.title' | 'page.notFound.title' | 'page.notFound.description' | 'page.lastUpdated.timeago' | 'page.lastUpdated.on' | 'catalog.filters.placeholder' | 'catalog.filters.title' | 'catalog.filters.clearAll' | 'catalog.filters.select.addFilter' | 'catalog.filters.select.all' | 'catalog.filters.done' | 'sidebar.menu.backLabel' | 'sidebar.menu.backToLabel' | 'sidebar.actions.show' | 'sidebar.actions.hide' | 'sidebar.actions.changeLayout' | 'versionPicker.label' | 'versionPicker.unversioned' | 'codeSnippet.copy.buttonText' | 'codeSnippet.copy.tooltipText' | 'codeSnippet.copy.toasterText' | 'markdown.editPage.text' | 'feedback.settings.comment.submitText' | 'feedback.settings.comment.label' | 'feedback.settings.comment.send' | 'feedback.settings.comment.cancel' | 'feedback.settings.comment.satisfiedLabel' | 'feedback.settings.comment.neutralLabel' | 'feedback.settings.comment.dissatisfiedLabel' | 'feedback.settings.submitText' | 'feedback.settings.label' | 'feedback.settings.reasons.label' | 'feedback.settings.reasons.send' | 'feedback.settings.comment.likeLabel' | 'feedback.settings.comment.dislikeLabel' | 'feedback.sentiment.thumbUp' | 'feedback.sentiment.thumbDown' | 'feedback.settings.leftScaleLabel' | 'feedback.settings.rightScaleLabel' | 'codeSnippet.report.buttonText' | 'codeSnippet.report.tooltipText' | 'codeSnippet.report.label' | 'userMenu.login' | 'userMenu.logout' | 'userMenu.devOnboardingLabel' | 'mobileMenu.mainMenu' | 'mobileMenu.previous' | 'mobileMenu.products' | 'page.nextButton' | 'page.previousButton' | 'openapi.download.description.title' | 'openapi.info.title' | 'openapi.info.contact.url' | 'openapi.info.contact.name' | 'openapi.info.license' | 'openapi.info.termsOfService' | 'openapi.info.metadata.title' | 'openapi.key' | 'openapi.value' | 'openapi.enum' | 'openapi.items' | 'openapi.default' | 'openapi.variable' | 'openapi.variables' | 'openapi.actions.show' | 'openapi.actions.hide' | 'openapi.actions.more' | 'openapi.languages.title' | 'openapi.servers.title' | 'openapi.operations' | 'openapi.webhooks' | 'openapi.description' | 'openapi.badges.deprecated' | 'openapi.badges.required' | 'openapi.badges.webhook' | 'openapi.request' | 'openapi.path' | 'openapi.query' | 'openapi.cookie' | 'openapi.header' | 'openapi.body' | 'openapi.responses' | 'openapi.response' | 'openapi.callbacks' | 'openapi.callbackRequest' | 'openapi.callbackResponse' | 'openapi.payload' | 'openapi.discriminator' | 'openapi.contentType' | 'openapi.tryIt' | 'openapi.loading' | 'openapi.example' | 'openapi.examples' | 'openapi.additionalProperties' | 'openapi.patternProperties' | 'openapi.required' | 'openapi.recursive' | 'openapi.complex' | 'openapi.deprecated' | 'openapi.hideExample' | 'openapi.showExample' | 'openapi.expandAll' | 'openapi.collapseAll' | 'openapi.noResponseExample' | 'openapi.noRequestPayload' | 'openapi.hidePattern' | 'openapi.showPattern' | 'openapi.authorizationUrl' | 'openapi.tokenUrl' | 'openapi.refreshUrl' | 'openapi.scopes' | 'openapi.security' | 'openapi.httpAuthorizationScheme' | 'openapi.bearerFormat' | 'openapi.parameterName' | 'openapi.flowType' | 'openapi.connectUrl' | 'openapi.requiredScopes' | 'openapi.unsupportedLanguage' | 'openapi.failedToGenerateCodeSample' | 'graphql.queries' | 'graphql.mutations' | 'graphql.subscriptions' | 'graphql.directives' | 'graphql.objects' | 'graphql.interfaces' | 'graphql.unions' | 'graphql.enums' | 'graphql.inputs' | 'graphql.scalars' | 'graphql.arguments.label' | 'graphql.arguments.show' | 'graphql.arguments.hide' | 'graphql.arguments.here' | 'graphql.returnTypes.label' | 'graphql.returnTypes.show' | 'graphql.returnTypes.hide' | 'graphql.possibleTypes' | 'graphql.defaultValue' | 'graphql.deprecationReason' | 'graphql.implementedInterfaces' | 'graphql.nonNull' | 'graphql.required' | 'graphql.deprecated' | 'graphql.variables' | 'graphql.querySample' | 'graphql.mutationSample' | 'graphql.subscriptionSample' | 'graphql.responseSample' | 'graphql.locations' | 'graphql.sample' | 'graphql.referenced';
|
|
2
|
+
export type TranslationKey = 'dev.newApp' | 'dev.newApp.text' | 'dev.sidebar.header' | 'dev.sidebar.footer.text' | 'dev.create.app.dialog.appName.placeholder' | 'dev.create.app.dialog.appName.error' | 'dev.create.app.dialog.selectAPIs' | 'dev.create.app.dialog.description' | 'dev.create.app.dialog.description.placeholder' | 'dev.create.app.dialog.create' | 'dev.create.app.dialog.cancel' | 'dev.main.tab.appKeys' | 'dev.main.tab.logs' | 'dev.app.description.title' | 'dev.edit.description.dialog.title' | 'dev.edit.description.dialog.save' | 'dev.edit.description.dialog.cancel' | 'dev.edit.apis.dialog.selectedAPIs' | 'dev.app.key.create' | 'dev.create.key.dialog.title' | 'dev.create.key.dialog.create' | 'dev.create.key.dialog.cancel' | 'dev.app.edit' | 'dev.app.delete' | 'dev.edit.app.dialog.title' | 'dev.edit.app.dialog.save' | 'dev.edit.app.dialog.cancel' | 'dev.delete.app.dialog.title' | 'dev.delete.app.dialog.confirmation' | 'dev.delete.app.dialog.delete' | 'dev.delete.app.dialog.cancel' | 'dev.app.key.roll' | 'dev.roll.key.dialog.title' | 'dev.roll.key.dialog.apiKey' | 'dev.roll.key.dialog.expires' | 'dev.roll.key.dialog.confirmation' | 'dev.roll.key.dialog.cancel' | 'dev.roll.key.dialog.roll' | 'dev.update.key.dialog.title' | 'dev.update.key.dialog.update' | 'dev.update.key.dialog.cancel' | 'dev.app.key.api.name' | 'dev.app.key.api.status' | 'dev.app.key.api.edit' | 'dev.edit.apis.dialog.title' | 'dev.edit.apis.dialog.apiKey' | 'dev.edit.apis.dialog.save' | 'dev.edit.apis.dialog.cancel' | 'dev.select.placeholder' | 'dev.app.overview.status.pending' | 'dev.app.overview.status.approved' | 'dev.app.overview.status.revoked' | 'dev.app.overview.status' | 'dev.app.overview.non-production' | 'dev.app.overview.production' | 'dev.app.overview.clientId' | 'dev.app.overview.apiKey' | 'dev.app.key.revoke' | 'dev.revoke.key.dialog.title' | 'dev.revoke.key.dialog.apiKey' | 'dev.revoke.key.dialog.expires' | 'dev.revoke.key.dialog.confirmation' | 'dev.revoke.key.dialog.revoke' | 'dev.revoke.key.dialog.cancel' | 'dev.app.overview.expires' | 'dev.app.overview.created' | 'dev.app.overview.visibilityToggle.hide' | 'dev.app.overview.visibilityToggle.show' | 'search.loading' | 'search.noResults.title' | 'search.keys.navigate' | 'search.keys.select' | 'search.keys.exit' | 'search.label' | 'search.cancel' | 'search.recent' | 'search.navbar.label' | 'search.suggested' | 'search.showMore' | 'search.filter.title' | 'search.filter.reset' | 'search.filter.field.reset' | 'search.ai.thinkingText' | 'search.ai.resourcesFound' | 'search.aiButton' | 'search.ai.label' | 'toc.header' | 'footer.copyrightText' | 'page.homeButton' | 'page.forbidden.title' | 'page.notFound.title' | 'page.notFound.description' | 'page.lastUpdated.timeago' | 'page.lastUpdated.on' | 'catalog.filters.placeholder' | 'catalog.filters.title' | 'catalog.filters.clearAll' | 'catalog.filters.select.addFilter' | 'catalog.filters.select.all' | 'catalog.filters.done' | 'sidebar.menu.backLabel' | 'sidebar.menu.backToLabel' | 'sidebar.actions.show' | 'sidebar.actions.hide' | 'sidebar.actions.changeLayout' | 'versionPicker.label' | 'versionPicker.unversioned' | 'codeSnippet.copy.buttonText' | 'codeSnippet.copy.tooltipText' | 'codeSnippet.copy.toasterText' | 'markdown.editPage.text' | 'feedback.settings.comment.submitText' | 'feedback.settings.comment.label' | 'feedback.settings.comment.send' | 'feedback.settings.comment.cancel' | 'feedback.settings.comment.satisfiedLabel' | 'feedback.settings.comment.neutralLabel' | 'feedback.settings.comment.dissatisfiedLabel' | 'feedback.settings.submitText' | 'feedback.settings.label' | 'feedback.settings.reasons.label' | 'feedback.settings.reasons.send' | 'feedback.settings.comment.likeLabel' | 'feedback.settings.comment.dislikeLabel' | 'feedback.sentiment.thumbUp' | 'feedback.sentiment.thumbDown' | 'feedback.settings.leftScaleLabel' | 'feedback.settings.rightScaleLabel' | 'feedback.settings.anonymousUserEmail.placeholder' | 'feedback.settings.anonymousUserEmail.label' | 'codeSnippet.report.buttonText' | 'codeSnippet.report.tooltipText' | 'codeSnippet.report.label' | 'userMenu.login' | 'userMenu.logout' | 'userMenu.devOnboardingLabel' | 'mobileMenu.mainMenu' | 'mobileMenu.previous' | 'mobileMenu.products' | 'page.nextButton' | 'page.previousButton' | 'openapi.download.description.title' | 'openapi.info.title' | 'openapi.info.contact.url' | 'openapi.info.contact.name' | 'openapi.info.license' | 'openapi.info.termsOfService' | 'openapi.info.metadata.title' | 'openapi.key' | 'openapi.value' | 'openapi.enum' | 'openapi.items' | 'openapi.default' | 'openapi.variable' | 'openapi.variables' | 'openapi.actions.show' | 'openapi.actions.hide' | 'openapi.actions.more' | 'openapi.languages.title' | 'openapi.servers.title' | 'openapi.operations' | 'openapi.webhooks' | 'openapi.description' | 'openapi.badges.deprecated' | 'openapi.badges.required' | 'openapi.badges.webhook' | 'openapi.request' | 'openapi.path' | 'openapi.query' | 'openapi.cookie' | 'openapi.header' | 'openapi.body' | 'openapi.responses' | 'openapi.response' | 'openapi.callbacks' | 'openapi.callbackRequest' | 'openapi.callbackResponse' | 'openapi.payload' | 'openapi.discriminator' | 'openapi.contentType' | 'openapi.tryIt' | 'openapi.loading' | 'openapi.example' | 'openapi.examples' | 'openapi.additionalProperties' | 'openapi.patternProperties' | 'openapi.required' | 'openapi.recursive' | 'openapi.complex' | 'openapi.deprecated' | 'openapi.hideExample' | 'openapi.showExample' | 'openapi.expandAll' | 'openapi.collapseAll' | 'openapi.noResponseExample' | 'openapi.noRequestPayload' | 'openapi.hidePattern' | 'openapi.showPattern' | 'openapi.authorizationUrl' | 'openapi.tokenUrl' | 'openapi.refreshUrl' | 'openapi.scopes' | 'openapi.security' | 'openapi.httpAuthorizationScheme' | 'openapi.bearerFormat' | 'openapi.parameterName' | 'openapi.flowType' | 'openapi.connectUrl' | 'openapi.requiredScopes' | 'openapi.unsupportedLanguage' | 'openapi.failedToGenerateCodeSample' | 'graphql.queries' | 'graphql.mutations' | 'graphql.subscriptions' | 'graphql.directives' | 'graphql.objects' | 'graphql.interfaces' | 'graphql.unions' | 'graphql.enums' | 'graphql.inputs' | 'graphql.scalars' | 'graphql.arguments.label' | 'graphql.arguments.show' | 'graphql.arguments.hide' | 'graphql.arguments.here' | 'graphql.returnTypes.label' | 'graphql.returnTypes.show' | 'graphql.returnTypes.hide' | 'graphql.possibleTypes' | 'graphql.defaultValue' | 'graphql.deprecationReason' | 'graphql.implementedInterfaces' | 'graphql.nonNull' | 'graphql.required' | 'graphql.deprecated' | 'graphql.variables' | 'graphql.querySample' | 'graphql.mutationSample' | 'graphql.subscriptionSample' | 'graphql.responseSample' | 'graphql.locations' | 'graphql.sample' | 'graphql.referenced';
|
|
3
3
|
export type Locale = {
|
|
4
4
|
code: string;
|
|
5
5
|
name: string;
|
|
@@ -10,8 +10,8 @@ const ChevronRightIcon_1 = require("../../../icons/ChevronRightIcon/ChevronRight
|
|
|
10
10
|
const CardImage_1 = require("../../../markdoc/components/Cards/CardImage");
|
|
11
11
|
const CardIcon_1 = require("../../../markdoc/components/Cards/CardIcon");
|
|
12
12
|
const Link_1 = require("../../../components/Link/Link");
|
|
13
|
-
function Card({ title, image, icon, iconRawContent, imagePosition = 'start', iconPosition = 'auto', layout = 'vertical', variant = 'filled', lineClamp, iconVariant, align = 'start', to, children, }) {
|
|
14
|
-
const titleNoSpaces = title.replace(/\s+/g, '-').toLowerCase();
|
|
13
|
+
function Card({ title = '', image, icon, iconRawContent, imagePosition = 'start', iconPosition = 'auto', layout = 'vertical', variant = 'filled', lineClamp, iconVariant, align = 'start', to, children, }) {
|
|
14
|
+
const titleNoSpaces = title === null || title === void 0 ? void 0 : title.replace(/\s+/g, '-').toLowerCase();
|
|
15
15
|
const cardTitleId = `card-title-${titleNoSpaces}`;
|
|
16
16
|
const justifyContent = align === 'center' ? 'center' : align === 'end' ? 'flex-end' : 'flex-start';
|
|
17
17
|
const alignItems = align === 'center' ? 'center' : align === 'end' ? 'flex-end' : 'flex-start';
|
|
@@ -5,6 +5,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.markdocExample = void 0;
|
|
7
7
|
const markdoc_1 = __importDefault(require("@markdoc/markdoc"));
|
|
8
|
+
// Walk through AST and look for raw markdown to parse
|
|
9
|
+
function parseInlineMarkdown(ast, config) {
|
|
10
|
+
if (Array.isArray(ast)) {
|
|
11
|
+
return ast.flatMap((child) => parseInlineMarkdown(child, config));
|
|
12
|
+
}
|
|
13
|
+
if (typeof ast === 'string') {
|
|
14
|
+
const parsed = markdoc_1.default.parse(ast);
|
|
15
|
+
return markdoc_1.default.transform(parsed, config);
|
|
16
|
+
}
|
|
17
|
+
if (ast && typeof ast === 'object' && ast.$$mdtype === 'Tag') {
|
|
18
|
+
const children = parseInlineMarkdown(ast.children, config);
|
|
19
|
+
return Object.assign(Object.assign({}, ast), { children });
|
|
20
|
+
}
|
|
21
|
+
return ast;
|
|
22
|
+
}
|
|
8
23
|
// This custom tag prevents evaluating any children markdoc tags (no children) so we can
|
|
9
24
|
// have markdoc examples in code fences
|
|
10
25
|
// approach copied from: https://github.com/markdoc/docs/blob/main/markdoc/tags/markdoc-example.markdoc.js
|
|
@@ -30,8 +45,10 @@ exports.markdocExample = {
|
|
|
30
45
|
const fenceWithTitle = annotations.find((annotation) => annotation.name === 'title');
|
|
31
46
|
title = fenceWithTitle === null || fenceWithTitle === void 0 ? void 0 : fenceWithTitle.value;
|
|
32
47
|
}
|
|
48
|
+
const demoContent = node.children[0].transformChildren(config);
|
|
49
|
+
const parsedDemoContent = parseInlineMarkdown(demoContent, config);
|
|
33
50
|
return new markdoc_1.default.Tag('MarkdocExample', Object.assign(Object.assign({}, attributes), { title,
|
|
34
|
-
language, demoContent:
|
|
51
|
+
language, demoContent: parsedDemoContent, rawContent: content }), []);
|
|
35
52
|
},
|
|
36
53
|
},
|
|
37
54
|
tagName: 'markdoc-example',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/theme",
|
|
3
|
-
"version": "0.46.
|
|
3
|
+
"version": "0.46.4",
|
|
4
4
|
"description": "Shared UI components lib",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"theme",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"timeago.js": "4.0.2",
|
|
80
80
|
"i18next": "22.4.15",
|
|
81
81
|
"nprogress": "0.2.0",
|
|
82
|
-
"@redocly/config": "0.19.
|
|
82
|
+
"@redocly/config": "0.19.2"
|
|
83
83
|
},
|
|
84
84
|
"scripts": {
|
|
85
85
|
"watch": "tsc -p tsconfig.build.json && (concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useEffect } from 'react';
|
|
3
3
|
import styled from 'styled-components';
|
|
4
|
-
import { ReasonsSettingsSchema } from '@redocly/config';
|
|
5
4
|
|
|
5
|
+
import type { AnonymousUserEmailSettings, ReasonsSettingsSchema } from '@redocly/config';
|
|
6
6
|
import type { ReasonsProps } from '@redocly/theme/components/Feedback/Reasons';
|
|
7
7
|
|
|
8
|
+
import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
|
|
8
9
|
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
9
10
|
import { RadioCheckButtonIcon } from '@redocly/theme/icons/RadioCheckButtonIcon/RadioCheckButtonIcon';
|
|
10
11
|
import { Comment } from '@redocly/theme/components/Feedback/Comment';
|
|
11
|
-
import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
|
|
12
12
|
import { Button } from '@redocly/theme/components/Button/Button';
|
|
13
13
|
import { FaceDissatisfiedIcon } from '@redocly/theme/icons/FaceDissatisfiedIcon/FaceDissatisfiedIcon';
|
|
14
14
|
import { FaceSatisfiedIcon } from '@redocly/theme/icons/FaceSatisfiedIcon/FaceSatisfiedIcon';
|
|
@@ -21,7 +21,12 @@ export enum MOOD_STATES {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export type MoodProps = {
|
|
24
|
-
onSubmit: (value: {
|
|
24
|
+
onSubmit: (value: {
|
|
25
|
+
score: number;
|
|
26
|
+
comment?: string;
|
|
27
|
+
reasons?: string[];
|
|
28
|
+
email?: string;
|
|
29
|
+
}) => void;
|
|
25
30
|
settings?: {
|
|
26
31
|
label?: string;
|
|
27
32
|
submitText?: string;
|
|
@@ -36,19 +41,33 @@ export type MoodProps = {
|
|
|
36
41
|
neutral?: ReasonsSettingsSchema;
|
|
37
42
|
dissatisfied?: ReasonsSettingsSchema;
|
|
38
43
|
};
|
|
44
|
+
anonymousUserEmail?: AnonymousUserEmailSettings;
|
|
39
45
|
};
|
|
40
46
|
className?: string;
|
|
41
47
|
};
|
|
42
48
|
|
|
43
49
|
export function Mood({ settings, onSubmit, className }: MoodProps): JSX.Element {
|
|
44
|
-
const {
|
|
50
|
+
const {
|
|
51
|
+
label,
|
|
52
|
+
submitText,
|
|
53
|
+
comment: commentSettings,
|
|
54
|
+
reasons: reasonsSettings,
|
|
55
|
+
anonymousUserEmail: anonymousUserEmailSettings,
|
|
56
|
+
} = settings || {};
|
|
45
57
|
const [score, setScore] = React.useState('');
|
|
46
58
|
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
47
59
|
const [comment, setComment] = React.useState('');
|
|
48
60
|
const [reasons, setReasons] = React.useState([] as ReasonsProps['settings']['items']);
|
|
49
|
-
const
|
|
61
|
+
const [email, setEmail] = React.useState<string>();
|
|
62
|
+
const { useTranslate, useUserMenu } = useThemeHooks();
|
|
63
|
+
const { userData } = useUserMenu();
|
|
50
64
|
const { translate } = useTranslate();
|
|
51
65
|
|
|
66
|
+
const onEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
67
|
+
const value = e.target.value;
|
|
68
|
+
setEmail(value || undefined);
|
|
69
|
+
};
|
|
70
|
+
|
|
52
71
|
const checkIfShouldDisplayReasons = (score: string) => {
|
|
53
72
|
if (!score || !reasonsSettings) {
|
|
54
73
|
return false;
|
|
@@ -112,26 +131,32 @@ export function Mood({ settings, onSubmit, className }: MoodProps): JSX.Element
|
|
|
112
131
|
const displayReasons = checkIfShouldDisplayReasons(score);
|
|
113
132
|
const displayComment = !!(score && !commentSettings?.hide);
|
|
114
133
|
const displaySubmitBnt = !!(score && (displayReasons || displayComment));
|
|
134
|
+
const displayFeedbackEmail =
|
|
135
|
+
!!score && anonymousUserEmailSettings?.enabled && !userData.isAuthenticated;
|
|
115
136
|
|
|
116
137
|
const onSubmitMoodForm = () => {
|
|
117
138
|
onSubmit({
|
|
118
139
|
score: remapScore(score),
|
|
119
140
|
comment,
|
|
120
141
|
reasons,
|
|
142
|
+
email,
|
|
121
143
|
});
|
|
122
144
|
setIsSubmitted(true);
|
|
123
145
|
};
|
|
124
146
|
|
|
125
147
|
const onCancelMoodForm = () => {
|
|
126
148
|
setScore('');
|
|
149
|
+
setComment('');
|
|
150
|
+
setReasons([]);
|
|
151
|
+
setEmail(undefined);
|
|
127
152
|
};
|
|
128
153
|
|
|
129
154
|
useEffect(() => {
|
|
130
|
-
if (score && !displayComment && !displayReasons) {
|
|
155
|
+
if (score && !displayComment && !displayReasons && !displayFeedbackEmail) {
|
|
131
156
|
onSubmitMoodForm();
|
|
132
157
|
}
|
|
133
158
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
134
|
-
}, [score, displayComment, displayReasons]);
|
|
159
|
+
}, [score, displayComment, displayReasons, displayFeedbackEmail]);
|
|
135
160
|
|
|
136
161
|
if (isSubmitted) {
|
|
137
162
|
return (
|
|
@@ -152,7 +177,7 @@ export function Mood({ settings, onSubmit, className }: MoodProps): JSX.Element
|
|
|
152
177
|
|
|
153
178
|
return (
|
|
154
179
|
<MoodWrapper data-component-name="Feedback/Mood" className={className}>
|
|
155
|
-
<StyledForm>
|
|
180
|
+
<StyledForm onSubmit={onSubmitMoodForm}>
|
|
156
181
|
<StyledFormMandatoryFields>
|
|
157
182
|
<Label data-translation-key="feedback.settings.label">
|
|
158
183
|
{label || translate('feedback.settings.label', 'Was this helpful?')}
|
|
@@ -213,12 +238,34 @@ export function Mood({ settings, onSubmit, className }: MoodProps): JSX.Element
|
|
|
213
238
|
)}
|
|
214
239
|
</StyledFormOptionalFields>
|
|
215
240
|
)}
|
|
241
|
+
|
|
242
|
+
{displayFeedbackEmail && (
|
|
243
|
+
<StyledFormOptionalFields>
|
|
244
|
+
<Label data-translation-key="feedback.settings.anonymousUserEmail.label">
|
|
245
|
+
{anonymousUserEmailSettings?.label ||
|
|
246
|
+
translate(
|
|
247
|
+
'feedback.settings.anonymousUserEmail.label',
|
|
248
|
+
'Your email (optional, for follow-up)',
|
|
249
|
+
)}
|
|
250
|
+
</Label>
|
|
251
|
+
<EmailInput
|
|
252
|
+
onChange={onEmailChange}
|
|
253
|
+
placeholder={
|
|
254
|
+
anonymousUserEmailSettings?.placeholder ||
|
|
255
|
+
translate('feedback.settings.anonymousUserEmail.placeholder', 'username@mail.com')
|
|
256
|
+
}
|
|
257
|
+
type="email"
|
|
258
|
+
required={!!email}
|
|
259
|
+
/>
|
|
260
|
+
</StyledFormOptionalFields>
|
|
261
|
+
)}
|
|
262
|
+
|
|
216
263
|
{displaySubmitBnt && (
|
|
217
264
|
<ButtonsContainer>
|
|
218
265
|
<Button onClick={onCancelMoodForm} variant="text" size="small">
|
|
219
266
|
Cancel
|
|
220
267
|
</Button>
|
|
221
|
-
<Button
|
|
268
|
+
<Button type="submit" variant="secondary" size="small">
|
|
222
269
|
Submit
|
|
223
270
|
</Button>
|
|
224
271
|
</ButtonsContainer>
|
|
@@ -291,3 +338,13 @@ const StyledMandatoryFieldContainer = styled.div`
|
|
|
291
338
|
align-items: center;
|
|
292
339
|
gap: var(--spacing-xxs);
|
|
293
340
|
`;
|
|
341
|
+
|
|
342
|
+
const EmailInput = styled.input`
|
|
343
|
+
background-color: var(--bg-color);
|
|
344
|
+
border-radius: var(--border-radius-lg);
|
|
345
|
+
border: var(--input-border);
|
|
346
|
+
outline: none;
|
|
347
|
+
color: var(--feedback-text-color);
|
|
348
|
+
font-family: var(--feedback-font-family);
|
|
349
|
+
padding: 10px;
|
|
350
|
+
`;
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import styled from 'styled-components';
|
|
3
2
|
import { useEffect } from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
4
|
|
|
5
|
+
import type { AnonymousUserEmailSettings } from '@redocly/config';
|
|
5
6
|
import type { ReasonsProps } from '@redocly/theme/components/Feedback/Reasons';
|
|
6
7
|
|
|
8
|
+
import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
|
|
7
9
|
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
8
10
|
import { RadioCheckButtonIcon } from '@redocly/theme/icons/RadioCheckButtonIcon/RadioCheckButtonIcon';
|
|
9
11
|
import { Comment } from '@redocly/theme/components/Feedback/Comment';
|
|
10
|
-
import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
|
|
11
12
|
import { Stars } from '@redocly/theme/components/Feedback/Stars';
|
|
12
13
|
import { Button } from '@redocly/theme/components/Button/Button';
|
|
13
14
|
|
|
14
15
|
export const FEEDBACK_MAX_RATING = 5;
|
|
15
16
|
|
|
16
17
|
export type RatingProps = {
|
|
17
|
-
onSubmit: (value: {
|
|
18
|
+
onSubmit: (value: {
|
|
19
|
+
score: number;
|
|
20
|
+
comment?: string;
|
|
21
|
+
reasons?: string[];
|
|
22
|
+
max: number;
|
|
23
|
+
email?: string;
|
|
24
|
+
}) => void;
|
|
18
25
|
settings?: {
|
|
19
26
|
label?: string;
|
|
20
27
|
submitText?: string;
|
|
@@ -28,43 +35,63 @@ export type RatingProps = {
|
|
|
28
35
|
component?: string;
|
|
29
36
|
items: string[];
|
|
30
37
|
};
|
|
38
|
+
anonymousUserEmail?: AnonymousUserEmailSettings;
|
|
31
39
|
};
|
|
32
40
|
className?: string;
|
|
33
41
|
};
|
|
34
42
|
|
|
35
43
|
export function Rating({ settings, onSubmit, className }: RatingProps): JSX.Element {
|
|
36
|
-
const {
|
|
44
|
+
const {
|
|
45
|
+
label,
|
|
46
|
+
submitText,
|
|
47
|
+
comment: commentSettings,
|
|
48
|
+
reasons: reasonsSettings,
|
|
49
|
+
anonymousUserEmail: anonymousUserEmailSettings,
|
|
50
|
+
} = settings || {};
|
|
37
51
|
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
38
52
|
const [score, setScore] = React.useState(0);
|
|
39
53
|
const [reasons, setReasons] = React.useState([] as ReasonsProps['settings']['items']);
|
|
40
54
|
const [comment, setComment] = React.useState('');
|
|
41
|
-
const
|
|
55
|
+
const [email, setEmail] = React.useState<string>();
|
|
56
|
+
const { useTranslate, useUserMenu } = useThemeHooks();
|
|
57
|
+
const { userData } = useUserMenu();
|
|
42
58
|
const { translate } = useTranslate();
|
|
43
59
|
|
|
60
|
+
const onEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
61
|
+
const value = e.target.value;
|
|
62
|
+
setEmail(value || undefined);
|
|
63
|
+
};
|
|
64
|
+
|
|
44
65
|
const onSubmitRatingForm = () => {
|
|
45
66
|
onSubmit({
|
|
46
67
|
score,
|
|
47
68
|
comment,
|
|
48
69
|
reasons,
|
|
49
70
|
max: FEEDBACK_MAX_RATING,
|
|
71
|
+
email,
|
|
50
72
|
});
|
|
51
73
|
setIsSubmitted(true);
|
|
52
74
|
};
|
|
53
75
|
|
|
54
76
|
const onCancelRatingForm = () => {
|
|
55
77
|
setScore(0);
|
|
78
|
+
setComment('');
|
|
79
|
+
setReasons([]);
|
|
80
|
+
setEmail(undefined);
|
|
56
81
|
};
|
|
57
82
|
|
|
58
83
|
const displayReasons = !!(score && reasonsSettings && !reasonsSettings.hide);
|
|
59
84
|
const displayComment = !!(score && !commentSettings?.hide);
|
|
60
85
|
const displaySubmitBnt = !!(score && (displayReasons || displayComment));
|
|
86
|
+
const displayFeedbackEmail =
|
|
87
|
+
!!score && anonymousUserEmailSettings?.enabled && !userData.isAuthenticated;
|
|
61
88
|
|
|
62
89
|
useEffect(() => {
|
|
63
|
-
if (score && !displayComment && !displayReasons) {
|
|
90
|
+
if (score && !displayComment && !displayReasons && !displayFeedbackEmail) {
|
|
64
91
|
onSubmitRatingForm();
|
|
65
92
|
}
|
|
66
93
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
67
|
-
}, [score, displayComment, displayReasons]);
|
|
94
|
+
}, [score, displayComment, displayReasons, displayFeedbackEmail]);
|
|
68
95
|
|
|
69
96
|
if (isSubmitted) {
|
|
70
97
|
return (
|
|
@@ -95,7 +122,6 @@ export function Rating({ settings, onSubmit, className }: RatingProps): JSX.Elem
|
|
|
95
122
|
<Stars max={FEEDBACK_MAX_RATING} onChange={setScore} value={score} />
|
|
96
123
|
</StyledMandatoryFieldContainer>
|
|
97
124
|
</StyledFormMandatoryFields>
|
|
98
|
-
|
|
99
125
|
{(displayReasons || displayComment) && (
|
|
100
126
|
<StyledFormOptionalFields>
|
|
101
127
|
{displayReasons && (
|
|
@@ -125,6 +151,28 @@ export function Rating({ settings, onSubmit, className }: RatingProps): JSX.Elem
|
|
|
125
151
|
)}
|
|
126
152
|
</StyledFormOptionalFields>
|
|
127
153
|
)}
|
|
154
|
+
|
|
155
|
+
{displayFeedbackEmail && (
|
|
156
|
+
<StyledFormOptionalFields>
|
|
157
|
+
<Label data-translation-key="feedback.settings.anonymousUserEmail.label">
|
|
158
|
+
{anonymousUserEmailSettings?.label ||
|
|
159
|
+
translate(
|
|
160
|
+
'feedback.settings.anonymousUserEmail.label',
|
|
161
|
+
'Your email (optional, for follow-up)',
|
|
162
|
+
)}
|
|
163
|
+
</Label>
|
|
164
|
+
<EmailInput
|
|
165
|
+
onChange={onEmailChange}
|
|
166
|
+
placeholder={
|
|
167
|
+
anonymousUserEmailSettings?.placeholder ||
|
|
168
|
+
translate('feedback.settings.anonymousUserEmail.placeholder', 'username@mail.com')
|
|
169
|
+
}
|
|
170
|
+
type="email"
|
|
171
|
+
required={!!email}
|
|
172
|
+
/>
|
|
173
|
+
</StyledFormOptionalFields>
|
|
174
|
+
)}
|
|
175
|
+
|
|
128
176
|
{displaySubmitBnt && (
|
|
129
177
|
<ButtonsContainer>
|
|
130
178
|
<Button onClick={onCancelRatingForm} variant="text" size="small">
|
|
@@ -185,3 +233,13 @@ const ButtonsContainer = styled.div`
|
|
|
185
233
|
margin-bottom: var(--spacing-xxs);
|
|
186
234
|
gap: var(--spacing-xxs);
|
|
187
235
|
`;
|
|
236
|
+
|
|
237
|
+
const EmailInput = styled.input`
|
|
238
|
+
background-color: var(--bg-color);
|
|
239
|
+
border-radius: var(--border-radius-lg);
|
|
240
|
+
border: var(--input-border);
|
|
241
|
+
outline: none;
|
|
242
|
+
color: var(--feedback-text-color);
|
|
243
|
+
font-family: var(--feedback-font-family);
|
|
244
|
+
padding: 10px;
|
|
245
|
+
`;
|
|
@@ -2,6 +2,7 @@ import * as React from 'react';
|
|
|
2
2
|
import { useEffect } from 'react';
|
|
3
3
|
import styled from 'styled-components';
|
|
4
4
|
|
|
5
|
+
import type { AnonymousUserEmailSettings } from '@redocly/config';
|
|
5
6
|
import type { ReasonsProps } from '@redocly/theme/components/Feedback/Reasons';
|
|
6
7
|
|
|
7
8
|
import { breakpoints } from '@redocly/theme/core/utils';
|
|
@@ -14,7 +15,13 @@ import { Button } from '@redocly/theme/components/Button/Button';
|
|
|
14
15
|
export const MAX_SCALE = 10;
|
|
15
16
|
|
|
16
17
|
export type ScaleProps = {
|
|
17
|
-
onSubmit: (value: {
|
|
18
|
+
onSubmit: (value: {
|
|
19
|
+
score: number;
|
|
20
|
+
comment?: string;
|
|
21
|
+
reasons?: string[];
|
|
22
|
+
max?: number;
|
|
23
|
+
email?: string;
|
|
24
|
+
}) => void;
|
|
18
25
|
settings?: {
|
|
19
26
|
label?: string;
|
|
20
27
|
submitText?: string;
|
|
@@ -30,6 +37,7 @@ export type ScaleProps = {
|
|
|
30
37
|
component?: string;
|
|
31
38
|
items: string[];
|
|
32
39
|
};
|
|
40
|
+
anonymousUserEmail?: AnonymousUserEmailSettings;
|
|
33
41
|
};
|
|
34
42
|
className?: string;
|
|
35
43
|
};
|
|
@@ -42,14 +50,22 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
|
|
|
42
50
|
rightScaleLabel,
|
|
43
51
|
comment: commentSettings,
|
|
44
52
|
reasons: reasonsSettings,
|
|
53
|
+
anonymousUserEmail: anonymousUserEmailSettings,
|
|
45
54
|
} = settings || {};
|
|
46
55
|
const [score, setScore] = React.useState(0);
|
|
47
56
|
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
48
57
|
const [comment, setComment] = React.useState('');
|
|
49
58
|
const [reasons, setReasons] = React.useState([] as ReasonsProps['settings']['items']);
|
|
50
|
-
const
|
|
59
|
+
const [email, setEmail] = React.useState<string>();
|
|
60
|
+
const { useTranslate, useUserMenu } = useThemeHooks();
|
|
61
|
+
const { userData } = useUserMenu();
|
|
51
62
|
const { translate } = useTranslate();
|
|
52
63
|
|
|
64
|
+
const onEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
65
|
+
const value = e.target.value;
|
|
66
|
+
setEmail(value || undefined);
|
|
67
|
+
};
|
|
68
|
+
|
|
53
69
|
const scaleOptions = [];
|
|
54
70
|
|
|
55
71
|
for (let i = 1; i <= MAX_SCALE; i++) {
|
|
@@ -69,11 +85,14 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
|
|
|
69
85
|
const displayReasons = !!score && reasonsSettings && !reasonsSettings.hide;
|
|
70
86
|
const displayComment = !!(score && !commentSettings?.hide);
|
|
71
87
|
const displaySubmitBnt = !!score && (displayReasons || displayComment);
|
|
88
|
+
const displayFeedbackEmail =
|
|
89
|
+
!!score && anonymousUserEmailSettings?.enabled && !userData.isAuthenticated;
|
|
72
90
|
|
|
73
91
|
const handleCancel = () => {
|
|
74
92
|
setScore(0);
|
|
75
93
|
setComment('');
|
|
76
94
|
setReasons([]);
|
|
95
|
+
setEmail(undefined);
|
|
77
96
|
};
|
|
78
97
|
|
|
79
98
|
const onSubmitScaleForm = () => {
|
|
@@ -82,16 +101,17 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
|
|
|
82
101
|
comment,
|
|
83
102
|
reasons,
|
|
84
103
|
max: MAX_SCALE,
|
|
104
|
+
email,
|
|
85
105
|
});
|
|
86
106
|
setIsSubmitted(true);
|
|
87
107
|
};
|
|
88
108
|
|
|
89
109
|
useEffect(() => {
|
|
90
|
-
if (score && !displayComment && !displayReasons) {
|
|
110
|
+
if (score && !displayComment && !displayReasons && !displayFeedbackEmail) {
|
|
91
111
|
onSubmitScaleForm();
|
|
92
112
|
}
|
|
93
113
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
94
|
-
}, [score, displayComment, displayReasons]);
|
|
114
|
+
}, [score, displayComment, displayReasons, displayFeedbackEmail]);
|
|
95
115
|
|
|
96
116
|
if (isSubmitted) {
|
|
97
117
|
return (
|
|
@@ -110,7 +130,7 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
|
|
|
110
130
|
|
|
111
131
|
return (
|
|
112
132
|
<ScaleWrapper data-component-name="Feedback/Scale" className={className}>
|
|
113
|
-
<StyledForm>
|
|
133
|
+
<StyledForm onSubmit={onSubmitScaleForm}>
|
|
114
134
|
<StyledFormMandatoryFields>
|
|
115
135
|
<Label data-translation-key="feedback.settings.label">
|
|
116
136
|
{label || translate('feedback.settings.label', 'Was this helpful?')}
|
|
@@ -156,6 +176,28 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
|
|
|
156
176
|
/>
|
|
157
177
|
)}
|
|
158
178
|
</StyledFormOptionalFields>
|
|
179
|
+
|
|
180
|
+
{displayFeedbackEmail && (
|
|
181
|
+
<StyledFormOptionalFields>
|
|
182
|
+
<Label data-translation-key="feedback.settings.anonymousUserEmail.label">
|
|
183
|
+
{anonymousUserEmailSettings?.label ||
|
|
184
|
+
translate(
|
|
185
|
+
'feedback.settings.anonymousUserEmail.label',
|
|
186
|
+
'Your email (optional, for follow-up)',
|
|
187
|
+
)}
|
|
188
|
+
</Label>
|
|
189
|
+
<EmailInput
|
|
190
|
+
onChange={onEmailChange}
|
|
191
|
+
placeholder={
|
|
192
|
+
anonymousUserEmailSettings?.placeholder ||
|
|
193
|
+
translate('feedback.settings.anonymousUserEmail.placeholder', 'username@mail.com')
|
|
194
|
+
}
|
|
195
|
+
type="email"
|
|
196
|
+
required={!!email}
|
|
197
|
+
/>
|
|
198
|
+
</StyledFormOptionalFields>
|
|
199
|
+
)}
|
|
200
|
+
|
|
159
201
|
{displaySubmitBnt && (
|
|
160
202
|
<ButtonsContainer>
|
|
161
203
|
<Button
|
|
@@ -169,7 +211,7 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
|
|
|
169
211
|
|
|
170
212
|
<Button
|
|
171
213
|
data-translation-key="feedback.settings.scale.send"
|
|
172
|
-
|
|
214
|
+
type="submit"
|
|
173
215
|
variant="secondary"
|
|
174
216
|
size="small"
|
|
175
217
|
>
|
|
@@ -259,3 +301,13 @@ const ScaleOptionsWrapper = styled.div`
|
|
|
259
301
|
gap: 2px;
|
|
260
302
|
}
|
|
261
303
|
`;
|
|
304
|
+
|
|
305
|
+
const EmailInput = styled.input`
|
|
306
|
+
background-color: var(--bg-color);
|
|
307
|
+
border-radius: var(--border-radius-lg);
|
|
308
|
+
border: var(--input-border);
|
|
309
|
+
outline: none;
|
|
310
|
+
color: var(--feedback-text-color);
|
|
311
|
+
font-family: var(--feedback-font-family);
|
|
312
|
+
padding: 10px;
|
|
313
|
+
`;
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useEffect } from 'react';
|
|
3
3
|
import styled from 'styled-components';
|
|
4
|
-
import { ReasonsSettingsSchema } from '@redocly/config';
|
|
5
4
|
|
|
5
|
+
import type { ReasonsSettingsSchema, AnonymousUserEmailSettings } from '@redocly/config';
|
|
6
6
|
import type { ReasonsProps } from '@redocly/theme/components/Feedback/Reasons';
|
|
7
7
|
|
|
8
|
+
import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
|
|
8
9
|
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
9
10
|
import { RadioCheckButtonIcon } from '@redocly/theme/icons/RadioCheckButtonIcon/RadioCheckButtonIcon';
|
|
10
11
|
import { Comment } from '@redocly/theme/components/Feedback/Comment';
|
|
11
|
-
import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
|
|
12
12
|
import { Button } from '@redocly/theme/components/Button/Button';
|
|
13
13
|
import { ThumbDownIcon } from '@redocly/theme/icons/ThumbDownIcon/ThumbDownIcon';
|
|
14
14
|
import { ThumbUpIcon } from '@redocly/theme/icons/ThumbUpIcon/ThumbUpIcon';
|
|
15
15
|
|
|
16
16
|
export type SentimentProps = {
|
|
17
|
-
onSubmit: (value: {
|
|
17
|
+
onSubmit: (value: {
|
|
18
|
+
score: number;
|
|
19
|
+
comment?: string;
|
|
20
|
+
reasons?: string[];
|
|
21
|
+
email?: string;
|
|
22
|
+
}) => void;
|
|
18
23
|
settings?: {
|
|
19
24
|
label?: string;
|
|
20
25
|
submitText?: string;
|
|
@@ -27,19 +32,33 @@ export type SentimentProps = {
|
|
|
27
32
|
like?: ReasonsSettingsSchema;
|
|
28
33
|
dislike?: ReasonsSettingsSchema;
|
|
29
34
|
};
|
|
35
|
+
anonymousUserEmail?: AnonymousUserEmailSettings;
|
|
30
36
|
};
|
|
31
37
|
className?: string;
|
|
32
38
|
};
|
|
33
39
|
|
|
34
40
|
export function Sentiment({ settings, onSubmit, className }: SentimentProps): JSX.Element {
|
|
35
|
-
const {
|
|
41
|
+
const {
|
|
42
|
+
label,
|
|
43
|
+
submitText,
|
|
44
|
+
comment: commentSettings,
|
|
45
|
+
reasons: reasonsSettings,
|
|
46
|
+
anonymousUserEmail: anonymousUserEmailSettings,
|
|
47
|
+
} = settings || {};
|
|
36
48
|
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
37
49
|
const [score, setScore] = React.useState(0);
|
|
38
50
|
const [comment, setComment] = React.useState('');
|
|
39
51
|
const [reasons, setReasons] = React.useState([] as ReasonsProps['settings']['items']);
|
|
40
|
-
const
|
|
52
|
+
const [email, setEmail] = React.useState<string>();
|
|
53
|
+
const { useTranslate, useUserMenu } = useThemeHooks();
|
|
54
|
+
const { userData } = useUserMenu();
|
|
41
55
|
const { translate } = useTranslate();
|
|
42
56
|
|
|
57
|
+
const onEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
58
|
+
const value = e.target.value;
|
|
59
|
+
setEmail(value || undefined);
|
|
60
|
+
};
|
|
61
|
+
|
|
43
62
|
const getScoreSpecificReasonSettings = (score: number) => {
|
|
44
63
|
switch (score) {
|
|
45
64
|
case 1:
|
|
@@ -68,6 +87,8 @@ export function Sentiment({ settings, onSubmit, className }: SentimentProps): JS
|
|
|
68
87
|
const displayReasons = checkIfShouldDisplayReasons(score);
|
|
69
88
|
const displayComment = !!(score && !commentSettings?.hide);
|
|
70
89
|
const displaySubmitBnt = !!(score && (displayReasons || displayComment));
|
|
90
|
+
const displayFeedbackEmail =
|
|
91
|
+
!!score && anonymousUserEmailSettings?.enabled && !userData.isAuthenticated;
|
|
71
92
|
|
|
72
93
|
const commentLabel =
|
|
73
94
|
score === 1
|
|
@@ -100,20 +121,24 @@ export function Sentiment({ settings, onSubmit, className }: SentimentProps): JS
|
|
|
100
121
|
score,
|
|
101
122
|
comment,
|
|
102
123
|
reasons,
|
|
124
|
+
email,
|
|
103
125
|
});
|
|
104
126
|
setIsSubmitted(true);
|
|
105
127
|
};
|
|
106
128
|
|
|
107
129
|
const onCancelSentimentForm = () => {
|
|
108
130
|
setScore(0);
|
|
131
|
+
setComment('');
|
|
132
|
+
setReasons([]);
|
|
133
|
+
setEmail(undefined);
|
|
109
134
|
};
|
|
110
135
|
|
|
111
136
|
useEffect(() => {
|
|
112
|
-
if (score && !displayComment && !displayReasons) {
|
|
137
|
+
if (score && !displayComment && !displayReasons && !displayFeedbackEmail) {
|
|
113
138
|
onSubmitSentimentForm();
|
|
114
139
|
}
|
|
115
140
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
116
|
-
}, [score, displayComment, displayReasons]);
|
|
141
|
+
}, [score, displayComment, displayReasons, displayFeedbackEmail]);
|
|
117
142
|
|
|
118
143
|
if (isSubmitted) {
|
|
119
144
|
return (
|
|
@@ -186,6 +211,28 @@ export function Sentiment({ settings, onSubmit, className }: SentimentProps): JS
|
|
|
186
211
|
)}
|
|
187
212
|
</StyledFormOptionalFields>
|
|
188
213
|
)}
|
|
214
|
+
|
|
215
|
+
{displayFeedbackEmail && (
|
|
216
|
+
<StyledFormOptionalFields>
|
|
217
|
+
<Label data-translation-key="feedback.settings.anonymousUserEmail.label">
|
|
218
|
+
{anonymousUserEmailSettings?.label ||
|
|
219
|
+
translate(
|
|
220
|
+
'feedback.settings.anonymousUserEmail.label',
|
|
221
|
+
'Your email (optional, for follow-up)',
|
|
222
|
+
)}
|
|
223
|
+
</Label>
|
|
224
|
+
<EmailInput
|
|
225
|
+
onChange={onEmailChange}
|
|
226
|
+
placeholder={
|
|
227
|
+
anonymousUserEmailSettings?.placeholder ||
|
|
228
|
+
translate('feedback.settings.anonymousUserEmail.placeholder', 'username@mail.com')
|
|
229
|
+
}
|
|
230
|
+
type="email"
|
|
231
|
+
required={!!email}
|
|
232
|
+
/>
|
|
233
|
+
</StyledFormOptionalFields>
|
|
234
|
+
)}
|
|
235
|
+
|
|
189
236
|
{displaySubmitBnt && (
|
|
190
237
|
<ButtonsContainer>
|
|
191
238
|
<Button onClick={onCancelSentimentForm} variant="text" size="small">
|
|
@@ -256,3 +303,13 @@ const ButtonsContainer = styled.div`
|
|
|
256
303
|
margin-bottom: var(--spacing-xxs);
|
|
257
304
|
gap: var(--spacing-xxs);
|
|
258
305
|
`;
|
|
306
|
+
|
|
307
|
+
const EmailInput = styled.input`
|
|
308
|
+
background-color: var(--bg-color);
|
|
309
|
+
border-radius: var(--border-radius-lg);
|
|
310
|
+
border: var(--input-border);
|
|
311
|
+
outline: none;
|
|
312
|
+
color: var(--feedback-text-color);
|
|
313
|
+
font-family: var(--feedback-font-family);
|
|
314
|
+
padding: 10px;
|
|
315
|
+
`;
|
package/src/core/types/l10n.ts
CHANGED
|
@@ -129,6 +129,8 @@ export type TranslationKey =
|
|
|
129
129
|
| 'feedback.sentiment.thumbDown'
|
|
130
130
|
| 'feedback.settings.leftScaleLabel'
|
|
131
131
|
| 'feedback.settings.rightScaleLabel'
|
|
132
|
+
| 'feedback.settings.anonymousUserEmail.placeholder'
|
|
133
|
+
| 'feedback.settings.anonymousUserEmail.label'
|
|
132
134
|
| 'codeSnippet.report.buttonText'
|
|
133
135
|
| 'codeSnippet.report.tooltipText'
|
|
134
136
|
| 'codeSnippet.report.label'
|
|
@@ -7,7 +7,7 @@ import { CardIcon } from '@redocly/theme/markdoc/components/Cards/CardIcon';
|
|
|
7
7
|
import { Link } from '@redocly/theme/components/Link/Link';
|
|
8
8
|
|
|
9
9
|
export type CardProps = React.PropsWithChildren<{
|
|
10
|
-
title
|
|
10
|
+
title?: string;
|
|
11
11
|
image?: string;
|
|
12
12
|
icon?: string;
|
|
13
13
|
iconRawContent?: string;
|
|
@@ -22,7 +22,7 @@ export type CardProps = React.PropsWithChildren<{
|
|
|
22
22
|
}>;
|
|
23
23
|
|
|
24
24
|
export function Card({
|
|
25
|
-
title,
|
|
25
|
+
title = '',
|
|
26
26
|
image,
|
|
27
27
|
icon,
|
|
28
28
|
iconRawContent,
|
|
@@ -36,7 +36,7 @@ export function Card({
|
|
|
36
36
|
to,
|
|
37
37
|
children,
|
|
38
38
|
}: CardProps) {
|
|
39
|
-
const titleNoSpaces = title
|
|
39
|
+
const titleNoSpaces = title?.replace(/\s+/g, '-').toLowerCase();
|
|
40
40
|
const cardTitleId = `card-title-${titleNoSpaces}`;
|
|
41
41
|
const justifyContent =
|
|
42
42
|
align === 'center' ? 'center' : align === 'end' ? 'flex-end' : 'flex-start';
|
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
import markdoc from '@markdoc/markdoc';
|
|
2
2
|
|
|
3
|
-
import type { Config, Node } from '@markdoc/markdoc';
|
|
3
|
+
import type { Config, Node, RenderableTreeNode } from '@markdoc/markdoc';
|
|
4
4
|
import type { MarkdocSchemaWrapper } from '@redocly/theme/markdoc/tags/types';
|
|
5
5
|
|
|
6
|
+
// Walk through AST and look for raw markdown to parse
|
|
7
|
+
function parseInlineMarkdown(
|
|
8
|
+
ast: RenderableTreeNode | RenderableTreeNode[],
|
|
9
|
+
config: Config,
|
|
10
|
+
): RenderableTreeNode | RenderableTreeNode[] {
|
|
11
|
+
if (Array.isArray(ast)) {
|
|
12
|
+
return ast.flatMap((child) => parseInlineMarkdown(child, config));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (typeof ast === 'string') {
|
|
16
|
+
const parsed = markdoc.parse(ast);
|
|
17
|
+
return markdoc.transform(parsed, config);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (ast && typeof ast === 'object' && ast.$$mdtype === 'Tag') {
|
|
21
|
+
const children = parseInlineMarkdown(ast.children, config);
|
|
22
|
+
return { ...ast, children } as RenderableTreeNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return ast;
|
|
26
|
+
}
|
|
27
|
+
|
|
6
28
|
// This custom tag prevents evaluating any children markdoc tags (no children) so we can
|
|
7
29
|
// have markdoc examples in code fences
|
|
8
30
|
// approach copied from: https://github.com/markdoc/docs/blob/main/markdoc/tags/markdoc-example.markdoc.js
|
|
@@ -29,13 +51,16 @@ export const markdocExample: MarkdocSchemaWrapper = {
|
|
|
29
51
|
title = fenceWithTitle?.value;
|
|
30
52
|
}
|
|
31
53
|
|
|
54
|
+
const demoContent = node.children[0].transformChildren(config);
|
|
55
|
+
const parsedDemoContent = parseInlineMarkdown(demoContent, config);
|
|
56
|
+
|
|
32
57
|
return new markdoc.Tag(
|
|
33
58
|
'MarkdocExample',
|
|
34
59
|
{
|
|
35
60
|
...attributes,
|
|
36
61
|
title,
|
|
37
62
|
language,
|
|
38
|
-
demoContent:
|
|
63
|
+
demoContent: parsedDemoContent,
|
|
39
64
|
rawContent: content,
|
|
40
65
|
},
|
|
41
66
|
[],
|